leetcode每日一题05.23——76.最小覆盖子串 及 滑动窗口算法框架

滑动窗口?

对于字符串和数组的子序列(子串是一个特殊的子序列)问题,我们都可以使用暴力解法遍历所有的子串,在子串中寻找是否符合题目要求的。这种遍历所有子串的算法也是滑动算法的一种,是不加约束的,没有约束就会导致很多不必要的计算,所以对于能暴力滑动窗口解决子串类的题目。我们都可以考虑通过添加约束来减少时间复杂度,来达到想要的结果。

什么时候用滑动窗口算法?

我个人认为,解决字符串和数组的子串或子序列问题,都可以使用滑动窗口算法,且尽量的加上约束

滑动窗口算法框架

下面举出一个滑动窗口算法的框架模板,两处...分别表示窗口右移和左移时候的操作,有很多情况该两处操作是对称的

滑动窗口的核心是左右指针的滑动右指针的滑动是为了寻找一个可行解,左指针的滑动是为了寻找一个最优解.左指针每滑动一次,我们就需要用一个值local_min记录该解一次.local_min保留的是从开始到目前位置的局部最优解.当遍历了整个子串之后,我们所剩下的local_min就是整体最优解了。

  • s表示源串,t表示子串.
  • need表示题目要求子串所应该满足的条件。
  • window表示目前窗口所包含子串里面的信息。
  • valid表示window当中已经满足need要求的字符个数 的数量。
/* 滑动窗口算法框架     来源Leetcode  */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    int local_min;
    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
        	
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            //记录该解.
        	local_min=left;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

作者:labuladong
链接:https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-suan-fa-tong-yong-si-xiang-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

例题

例题一 leetcode 3.无重复字符的最长字串

无重复字符的最长字串
在这里插入图片描述
解题思路:
首先分析右指针和左指针滑动的时候我们要做的事情:
在此题中,滑动窗口应该是不含有重复字符的子串.
在该问题当中,右指针右移就是寻找可行解,同时也是寻找最优解的过程。(因为题目要求最长)。

我们用window来表示当前窗口各个字符的数目。

  1. 纳入right指向的字符,之后判断窗口中是否存在重复字符
  2. 不存在重复字符,该窗口的解是一个可行解.同时也是当前的最优解。
  3. 存在重复字符,需要将左端窗口收缩,直至不存在重复字符为止,无重复字符的窗口也是一个可行解,记录该解。

代码:


//滑动窗口的关键是找到可行解,在可行解中寻找最优解的过程。
//对本次题来说,right指向了当前窗口最后一个元素的下一个位置.
//将right位置的字符纳入窗口,判断窗口中是否存在相同元素.无重复的话这就是一个可行解,记录长度。
//存在重复的话,就开始移动left.直至没有重复为止,记录长度。
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.length()==0){
            return 0;
        }
        map<char,int> window;
        int left=0,right=0;
        int len=0;

        while(right<s.length()){
            //c为移入窗口的字符
            char c=s[right];
            window[c]++;
            right++;
            //通过window[c]来判断是否存在重复。
            while(window[c]>1){
                char d=s[left];
                window[d]--;
                left++;  
            }
            len=max(len,right-left);
        }
        return len;
    }
};

例题二 leetocde567.字符串的排列

字符串的排列
在这里插入图片描述
这道题第一眼看起来好像需要求s1的所有排列,然后依次判断s2中是否包含该排列。
但是有更好的思路,就是求s是否存在一个子串,该子串包含t中的所有字符且不包含其他字符?
这种子串类问题,我们要下意识的考虑到滑动窗口算法。

算法思路:移入元素时考虑是否当前元素是否满足need的要求,满足的话就valid++,如果窗口长度大于等于s1长度,就要收缩滑动窗口。收缩的时候判断是否产生了我们要求的解。然后记录左端窗口元素.将其移出滑动窗口即可,最后如果都没有找到解,就返回false.
代码

//判断S是否存在一个子串,使得该子串只包含t中的所有字符而不包含其他字符.

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        map<char,int> need,window;
        for(int i=0;i<s1.length();i++){
            need[s1[i]]++;
        }
        int left=0,right=0;
        //记录已经达到need要求的字符的个数.
        int valid=0;

        while(right<s2.length()){
            char c=s2[right];
            right++;
            //我们需要这个字符.
            if(need.find(c)!=need.end()){
                window[c]++;
                if(window[c]==need[c]){
                    valid++;
                }
            }
            //左端收缩的条件
            while(right-left>=s1.length()){
                //收缩的时候可能产生解。
                if(valid==need.size()) return true;
                char d=s2[left];

                left++;
                if(need.find(d)!=need.end()){
                    if(window[d]==need[d])
                        valid--;
                    window[d]--;
                }
            }
        }
        return false;
    }
};

例题3 leetcode438找到字符串中所有字母异位词

在这里插入图片描述

这题和例题二是一样的套路,问在s串的所有的子串当中,找到只包含t中所有字符串的子串。
代码


//注意,本身也是本身的异位词.

//在s中寻找子串,要求该子串

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        map<char,int> need,window;
        for(int i=0;i<p.length();i++){
            need[p[i]]++;
        }
        int left=0,right=0;
        vector<int> res;
        int valid=0;
        while(right<s.length()){
            char c=s[right];
            right++;
            //如果p中包含该字符,就纳入window中.
            if(need.find(c)!=need.end()){
                window[c]++;
                if(window[c]==need[c])
                    valid++;
            }
            //左指针滑动.
            while(right-left>=p.length()){
                if(valid==need.size()) res.push_back(left);
                char d=s[left];
                left++;
                if(need.find(d)!=need.end()){
                    if(need[d]==window[d])
                        valid--;
                    window[d]--;
                }
            }
        }
        return res;
    }
};

例题4 leetcode76.最小覆盖子串

在这里插入图片描述
这题是要求一个S的子串,使得该子串包含T中的所有字符,且该子串的长度最小.
解题思路:我们先拉长滑动窗口求出一个可以满足包含T的所有字符的解,然后缩短窗口求出一个最优解。
代码


//我们要求j-i的最小值,并输出string[i]->string[j].

//dp不会.采用题解进行双指针解决.  
//整体思路:先找到可行解,再优化可行解.直至最后。
//怎么找可行解? 右指针右移直到左右指针的窗口包含了t字符串中的所有字符.这是可行解,记录该解.
//怎么优化可行解? 找到可行解之后,将左指针右移.直到不包含t字符串中的所有字符为止.且每左移一次,就将要返回的答案更新一次.
//当右指针到了末尾的时候,当前的最优解就是整体的最优解了.
class Solution {
public:
    string minWindow(string s, string t) {

        map<char,int> need,window; //need表示需要的字符个数, widow表示窗口已经有的字符个数.
        for(int i=0;i<t.length();i++){
            need[t[i]]++;  //如果没有会自动创建.
        }
        int left=0,right=0;  //要滑动的2个指针.
        int valid=0;  //对某个字符,窗口中含有的已经等于需要的.

        int start=0;
        int len=INT_MAX;
        while(right<s.length()){
            //滑动窗口的区间是左闭右开.
            char tmp=s[right];
            right++;
            if(need.count(tmp)){
                window[tmp]++;
                if(window[tmp]==need[tmp])
                    valid++;
            }
            //找到了可行解,开始更新它
            if(valid==need.size()){  
                while(valid==need.size()){
                    //保留该可行解
                    if(right-left<len){
                        start = left;
                        len = right - left;
                    }
                    //缩小窗口.
                    char c=s[left];
                    left++;
                    if(need.count(c)){
                        if(window[c]==need[c])
                            valid--;
                        window[c]--;
                    }
                }
            }
        }
        return len==INT_MAX? "" :s.substr(start, len);
    }

};

总结

这篇文章写了滑动窗口算法框架。当我们想要解决字符串的子串匹配问题时候,我们要多考虑一下滑动窗口算法~!

再附上一个leetcode的滑动窗口算法链接,总结的挺好的.之后也可以看一下~
将滑动窗口算法变成默写题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值