【刷题】滑动窗口精通 — Leetcode 30. 串联所有单词的子串 | Leetcode 76. 最小覆盖子串

简介: 经过这两道题目的书写,相信大家一定深刻认识到了滑动窗口的使用方法!!!下面请大家继续刷题吧!!!

送给大家一句话:

充满着欢乐与斗争精神的人们,永远带着欢乐,欢迎雷霆与阳光。 —— 赫胥黎

滑动窗口精通

前言

相信通过前两篇的文章的讲解,大家已经对滑动窗口有了较深的认识,今天我们来挑战一下!!!

来做两道困难级的题目。

Leetcode 30. 串联所有单词的子串

家人们!!!上链接!!!30. 串联所有单词的子串

题目描述

根据题目描述,这道题看起来很是复杂呢!?!?

我们要在一个字符串中寻找包含words的所有单词的子串,并会返回对应的开始位置(开始索引)。看这描述十分类似这道题目 438. 找到字符串中所有字母异位词 ,一个是寻找异位词,一个是寻找"异位单词串"。本质是十分相似的。

来看一个样例:

  • 输入:s = “barfoothefoobarman”, words = [“foo”,“bar”]
  • 输出:[0,9]
  • 解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。

子串 “barfoo” 开始位置是 0。它是 words 中以 [“bar”,“foo”] 顺序排列的连接。

子串 “foobar” 开始位置是 9。它是 words 中以 [“foo”,“bar”] 顺序排列的连接。

  • 输出顺序无关紧要。返回 [9,0] 也是可以的。

根据这样例,更容易想象为是如同字母一样。

算法思路

我们先把每个单词抽象为一个字母(方便我们梳理思路),我们只需要找到一个子串中有所有的“字母”即可。

那么,我们来看最简单的算法:暴力枚举。就是遍历所有的子串,以样例来说:

  1. bar foo the foo bar man (注意步长是单词的长度),从左到右依次遍历,寻找满足条件的子串。
  2. 我们来思考一下,当该躺不满足条件时:

很明显,无需把right重新移动的到left的位置重新开始,直接继续进行移动就可以。

3.所以此时构成滑动窗口的条件两个指针移动方向一致

那么我们就按照滑动窗口的解题模版来思考细节:

  • 进窗口
  • 判断
  • 出窗口
  • 更新结果(位置待定)

首先我们要解决的是个一般性问题:s 字符串的长度一定是单词的整数倍吗???

显然不是!

那么就要进行多次滑动窗口!保证可以遍历到所有可能的子串。那进行几次呢???

可以看出来只需要进行单词个数次的循环即可!!!再多就发生重复了!

这样大致的框架就有了,剩下的就是然后判断单词是否满足条件。

依旧时候使用哈希算法,利用无向图来模拟哈希表(string 映射 int)。'

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        //预备工作
        //哈希表1
        unordered_map<string,int> hash1;
        for(auto ch : words) hash1[ch] ++; //对words进行处理
        // 基本变量
        int len = words[0].size(); //单词长度(步长) 
        int m = words.size();//单词个数
        int n = s.size();//字符串长度
        //答案
        vector<int> ans;
        //多次进行滑动窗口
        for(int i = 0;i < len;i++)
        {
            //哈希表2 用来进行比较
            unordered_map<string,int> hash2;
            //进窗口 注意一次移动的步长 注意结束条件(保证最后一个是完整单词,不然会发生越界)
            for(int left = i ,right = i ,count = 0; right + len <= n ; right += len )
            {
                string in = s.substr(right,len);
                //进窗口 维护count++ (有效单词数加一)
                if(++hash2[in] <= hash1[in])
                {
                    count++;
                }
                //判断是否超出长度
                if(right - left + 1 > m * len)
                {
                    string out = s.substr(left,len);
                    //出窗口 维护count
                    if(hash2[out]-- <= hash1[out]) count--;
                    left += len;
                }
                //满足条件  更新结果
                if(count == m)
                {
                    ans.push_back(left);
                }
            } 
        }
        return ans;
    }
};

我们提交:过啦!!!!

Leetcode 76. 最小覆盖子串

家人们!!! 上链接!!!76. 最小覆盖子串

题目描述

根据题目描述,我们需要再字符串中寻找能够覆盖 t 中所有字符的 最短子串,这个“覆盖”是包含 t 中的每个字母,不用管顺序。来看样例:

  • 输入:s = “ADOBECODEBANC”, t = “ABC”
  • 输出:“BANC”
  • 解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。

看了样例,应该就理解了这个“覆盖”:

  1. 对应字母个数必须大于等于 t 中字母个数
  2. 可以包含其它字母

算法思路

先来最直接的办法 — 暴力枚举,我们来看暴力算法是如何进行的:

  1. 首先找到一个包含于 t 的字母
  2. 然后从这个位置开始遍历
  3. 直到满足 t 中的每个字母都能在该子串中找到
  4. 然后再找到下一个包含于 t 的字母 重复 1 - 3

这样就完成了任务,那么如何进行优化呢???

根据暴力的步骤,我们可以看出来两个指针的移动方向是一致的,所以就可以进行滑动窗口算法了。

那么我们就按照滑动窗口的解题模版来思考细节:

  • 进窗口
  • 判断
  • 出窗口
  • 更新结果(位置待定)

这个判断要怎样进行判断???

肯定是使用哈希算法,但是如何保证对应字母个数必须大于等于 t 中字母个数呢???

这一步与之前的判断略有不同,因为我们可以多字母,来看代码:

class Solution {
public:
    string minWindow(string s, string t) {
    //滑动窗口 + 哈希算法
    
    //准备工作
    int kinds = 0; //字母种类 方便判断
    int hash1[128] = {0}; //哈希表1 因为只有使用数组比较方便
    //遍历所有字母 确定个数
    for (auto ch : t) 
        if (hash1[ch]++ == 0) kinds++;
    // 长度 
    int n = s.size();
    //哈希表2 用来比较
    int hash2[128] = { 0 }; 
    int begin = -1 ,minlen = INT_MAX;
    for(int left = 0,right = 0 ,count = 0;right < n;right++)
    {
        
        char in = s[right];
        //进窗口 维护count (对应字母相等时更新 保证满足条件 有效字母加一)
        if(++hash2[in] == hash1[in]) count++;
        //判断
        while(count == kinds)
        {
            //更新结果
            if(right - left + 1 < minlen)
            {
                minlen = right - left + 1;
                begin = left;
            }
            char out = s[left];
            //出窗口 维护count (相等时出窗口 有效字母减少)
            if(hash2[out]-- == hash1[out]) count--;
            left++;
        }
    }
    return begin == -1 ? "" : s.substr(begin,minlen);
    }
};

提交:过啦!!!!!!

经过这两道题目的书写,相信大家一定深刻认识到了滑动窗口的使用方法!!!

下面请大家继续刷题吧!!!

Thanks♪(・ω・)ノ 谢谢阅读!!!

下一篇文章见!!!

相关文章
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
1月前
【LeetCode 26】239.滑动窗口最大值
【LeetCode 26】239.滑动窗口最大值
31 1
|
17天前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
16 1
|
2月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
1月前
Leetcode(最后一个单词长度)
这篇文章介绍了两种解决LeetCode第58题的方法,即计算给定字符串中最后一个单词的长度,方法包括翻转字符串和逆向遍历统计。
18 0
|
1月前
【LeetCode 20】151.反转字符串里的单词
【LeetCode 20】151.反转字符串里的单词
19 0
|
1月前
【LeetCode 04】滑动窗口法总结
【LeetCode 04】滑动窗口法总结
17 0
|
3月前
|
算法
LeetCode第58题最后一个单词的长度
LeetCode第58题"最后一个单词的长度"的解题方法,通过从字符串末尾向前遍历并计数非空格字符,直接得出最后一个单词的长度。
LeetCode第58题最后一个单词的长度
|
3月前
|
Python
【Leetcode刷题Python】1467. 两个盒子中球的颜色数相同的概率
本文介绍了LeetCode第50题"Pow(x, n)"的解法,题目要求实现计算x的n次幂的函数,文章提供了递归分治法的详细解析和Python实现代码。
39 0
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
55 6