滑动窗口模板

滑动窗口模板

/* 滑动窗⼝算法框架 */
void slidingWindow(string s, string t) {
	unordered_map<char, int> need, window;
	for (char c : t) need[c]++;
	int left = 0, right = 0;
	int valid = 0;
	while (right < s.size()) {//左闭右开区间
		// c 是将移⼊窗⼝的字符
		char c = s[right];
		// 右移窗⼝
		right++;
		// 进⾏窗⼝内数据的⼀系列更新
		...
	
		// 判断左侧窗⼝是否要收缩
		while (window needs shrink) {
			// d 是将移出窗⼝的字符
			char d = s[left];
			// 左移窗⼝
			left++;
			// 进⾏窗⼝内数据的⼀系列更新
			...
		}
	}
}

76. 最小覆盖子串
在做滑动窗口问题时要明确以下几个问题

  1. 什么时候停止窗口扩张,哪些参数变化
  2. 什么时候停止窗口收缩,哪些参数变化
  3. 是在扩张时更新结果还是收缩时更新结果

对于本题而言,当窗口的字符包含t中的字符时停止扩张,当窗口的字符没有包含t中所有字符时停止收缩,因为这是求最小子串,所以应该是在收缩时更新结果。
准备两个哈希表window,need,need统计t中的词频,window统计s字符串包含t中字符的词频
例如 s=“aabcc”,t=“aa”,window只需要统计a的词频就好了,只要窗口中a的词频>=2就说明找到了一个子串
再准备一个变量valid用来判断是否停止扩张或者收缩、

补充:need.count (c) //查找以c为键的个数
need[c]:如果没有以c为键的键值对,就会自动创建一个

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char,int> window,need;//window只需要统计need中字符的词频
        int left=0;
        int right=0;//[)区间,所以初始时窗口为空
        int start=0;//子串的开始位置
        int len=INT_MAX;//子串的长度
        int valid=0;//窗口中符合条件的字符(字符是t中的每个字符)的个数,当valid==need的长度时就说明找到了一个子串
        string re;//保存最终结果
        for(char c:t){
            need[c]++;//统计t中各字符的个数
        }
        while(right<s.size()){
            char c=s[right];//预进入窗口的字符
            right++;//窗口右移
            if(need.count(c)){//当t中含有c字符时更新window
                window[c]++;
               
                if(window[c]==need[c]){//c字符的个数满足条件
                    valid++;
                }
            }

            while(valid==need.size()){//找到了一个子串,need.size()就是t中的字符数
                
                if(right-left<len){//更新最小子串
                    start=left;
                    len=right-left;
                }
                char d=s[left];
    
                left++;//窗口右移
                if(need.count(d)){//移出窗口的字符是符合条件的字符
                   
                    if(window[d]==need[d]){//窗口中d字符个数等于need中d的个数
                        valid--;
                    }
                    window[d]--;
                }

            }

        }
        re=len==INT_MAX?"":s.substr(start,len);
        return re;
    }
};

567. 字符串的排列
判断s2中是否有s1的某个全排列,所以就是找一个包含s1的某个全排列的窗口,如果这个窗口的大小就是s1的大小,直接返回true。
详细步骤:
开始,不断向右扩张,直到valid==need.size()即找到包含s1中所有字符的窗口,然后再进行收缩,收缩前判断窗口的大小是否等于s1的大小,若是,就直接返回true,否则进行收缩

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        unordered_map<char,int>window,need;
        
        int valid=0;
        int left=0;
        int right=0;

        for(char c:s1){
            need[c]++;
        }
        while(right<s2.size()){
            char in=s2[right];
            right++;
            if(need.count(in)){
                window[in]++;
                if(window[in]==need[in]){
                    valid++;
                }
            }

            while(valid==need.size()){
                if(right-left==s1.size()){//已找到s1的某个全排列
                    return true;
                }
                
                char out=s2[left];
                left++;
                if(need.count(out)){
                    window[out]--;
                    if(window[out]<need[out]){
                        valid--;
                    }
                }
            }

            
        }

       return false;
       
    }
};

438. 找到字符串中所有字母异位词

和上一题一样,也是找全排列的窗口

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        unordered_map<char,int>window,need;
        int left=0;
        int right=0;
        int valid=0;
        vector<int>re;
        for(char c:p){
            need[c]++;
        }
        while(right<s.size()){
            char in=s[right];
            right++;
            if(need.count(in)){
                window[in]++;
                if(window[in]==need[in]){
                    valid++;
                }
            }

            while(valid==need.size()){
                if(right-left==p.size()){//找到一个异位词
                    re.push_back(left);
                }
                char out=s[left];
                left++;
                if(need.count(out)){
                    window[out]--;
                    if(window[out]<need[out]){
                        valid--;
                    }
                }
            }
            
        }
        return re;
    }
};

3. 无重复字符的最长子串

扩张条件:只要当前窗口没有重复字符就扩张
收缩条件:正好与扩张条件相反,有重复字符就收缩
收集结果的时机:扩张时

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char ,int>window;
        int left=0;
        int right=0;
        int minLen=INT_MIN;
        while(right<s.size()){
            char c=s[right];
            right++;
            window[c]++;
            if(window[c]<=1){//要保证当前窗口没有重复值时才可以收集结果
                minLen=minLen>right-left?minLen:right-left;
            }
            while(window[c]>1){//收缩
                char d=s[left];
                left++;
                window[d]--;
            }
        }
        return minLen==INT_MIN?0:minLen;
    }
};

总结

模板固然好用,但是有时候可以要具体问题具体分析,像最后这一题,如果硬套模板反而更加复杂了,总之做滑动窗口题目时主要分析:扩张的条件,收缩的条件,收集结果的时机。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值