数组-滑动窗口

不要畏惧滑动窗口,其核心就是,什么时候扩大窗口(扩大一格还是多格),什么时候缩小窗口,什么时候记录答案。滑动窗口对应问题就是找子串,但要注意对于一些存在负数的情况,我们应该仔细审视。

76. 最小覆盖子串 - 力扣(LeetCode)

我们可以看到所谓的子串其实顺序,连续性都没有要求,第一个难点就是如何确定这种的边界条件。所以我们需要一个数据结构统计每个字符出现的次数,第二个难点,根据之前的三问,什么时候扩展窗口---当我们的窗口中覆盖了所有的子串所需字符(通过第一个难点解决),什么时候缩窗口---当扩展完后,我们开始缩窗口,直到再缩就不满足覆盖条件时,我们选择记录答案,也就是第三个问题。

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> need, window;
        for(char c : t) {
            need[c]++;
        }//初始化窗口
        int left = 0, right = 0;
        int valid = 0;//字符种类
        int len = INT_MAX;
        int start = 0;
        while(right < s.size()) {
            char c = s[right];
            right++;
            if(need.find(c) != need.end()) {
                window[c]++;
                if(need[c] == window[c]) {
                    valid++;
                }
            }
            while(valid == need.size()) {
                if(right - left < len) {
                    start = left;
                    len = right -left;
                }//记录答案
                char d = s[left];
                left++;
                if(need.find(d) != need.end()) {
                    if(window[d] == need[d]) {
                        valid--;
                    }
                    window[d]--;
                }
            }
        }
        return len == INT_MAX ? "" : s.substr(start,len);
    }
};

left 和right才是滑动窗口,window只是配合need的边界条件检查,start和len只是存储答案。我们扩展窗口和缩小窗口都选择while,缩小条件是每种字符串数量都满足即子串已经被完全覆盖。

另外就是要注意判断顺序,增减的顺序。

这道题是比较全面的一道题,后面的滑动窗口,可以大概视为其简化版本。

567. 字符串的排列 - 力扣(LeetCode)

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        unordered_map<char, int> need, window;
        for(const char& c : s1) {
            need[c]++;
        }
        int left = 0, right = 0;
        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(valid == need.size()) {
                if((right - left) == s1.length()) {
                    return true;
                }
                char d = s2[left];
                left++;
                if(need.find(d) != need.end()) {
                    if(window[d] == need[d]) {
                        valid--;
                    }
                    window[d]--;
                }
            }
        }
        return false;
    }
};

我们来分析一下这道题,将重点放在两道题的对比上,这道题最大的变化是什么?-----从覆盖变成了排列,什么意思呢,就是我们子串的长度是固定的。其他什么都没变。我们来仔细看一下代码,尤其是其变化的部分,第一:start和len没了,因为这两个参数就是为了求子串,但这道题是判断(bool)所以不需要那两个参数,第二:写答案的逻辑,也是我们主要改变的地方,我们可以看到,其改变就是针对子串长度固定这个问题,从记录最小覆盖的子串,变成了是否存在子串排列。如果你真的理解了这些变化,那么距离掌握滑动窗口就很近了。

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

小样,换个马甲我就认不出你了?什么异位词,不就是上一道题中的排列吗

在看不同,返回的不是bool了改成其实索引了。

直接写:

class Solution {
public:
    vector<int> findAnagrams(string s2, string s1) {
        unordered_map<char, int> need, window;
        for(const char& c : s1) {
            need[c]++;
        }
        int left = 0, right = 0;
        int valid = 0;
        vector<int> res;
        while(right < s2.length()) {
            char c = s2[right];
            right++;
            if(need.find(c) != need.end()) {
                window[c]++;
                if(window[c] == need[c]) {
                    valid++;
                }
            }
            while(valid == need.size()) {
                if((right - left) == s1.length()) {
                    res.push_back(left);
                }
                char d = s2[left];
                left++;
                if(need.find(d) != need.end()) {
                    if(window[d] == need[d]) {
                        valid--;
                    }
                    window[d]--;
                }
            }
        }
        return res;
    }
};

如果前两道完全掌握,你甚至连改几行代码都会提前想好,从返回bool改成起始地址了,那只需要改返回逻辑就好了,很容易秒杀。

3. 无重复字符的最长子串 - 力扣(LeetCode)

这道题可以作为练习,稍微有变化。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> window;
        int left = 0, right = 0;
        int max_len = 0;
        
        while (right < s.length()) {
            char c = s[right];
            right++;
            window[c]++;
            
            // 当窗口中字符 c 的数量大于 1 时,收缩窗口
            while (window[c] > 1) {
                char d = s[left];
                left++;
                window[d]--;
            }
            
            // 更新最大长度
            max_len = max(max_len, right - left);
        }
        
        return max_len;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值