滑动窗口

  • 最小窗口子序列-DP解法

滑动窗口是一种双指针技巧的算法框架, 其算法时间复杂度较低。
下面是leetcode中常见的几道可以使用滑动窗口解决的题目。

一、最小覆盖字串

题目链接:https://leetcode-cn.com/problems/minimum-window-substring/
题目要求在S(source) 中找到包含T(target)中全部字母的一个字串,顺序无所谓,要求这个字串的最短长度。

暴力解法,代码大概如下:

for(int i=0;i<s.size();i++)
	for(int j=i+1,j<s.size();j++)
		if(s[i:j] 包含t中所有字母)
			更新答案

思路很简单,但是算法时间复杂度为O(n^2).
滑动窗口的思路如下:

  1. 在字符串S中使用双指针,初始化left = right = 0, 将索引闭区间[left, right]称为一个窗口
  2. 首先不断增加right,直到窗口内的字符串符合要求,包含了T中所有字符
  3. 此时,停止增加right,减少left直到窗口不再满足条件,每次减少的同时更新结果
  4. 重复第2、3 步直到right到达字符串S的末尾

简单理解就是,第二步先找到一个可行解,然后第三步优化这个可行解,不断重复第二三步直到找到最终解。
解题代码:

    string minWindow(string s, string t) {
        // 特殊情况 
        if(t.length()>s.length() || t=="") return "";
        // 滑动窗口
        int l=0,r=0;

        // 数组代替hash_map 记录字符出现的次数
        int array_s[256]={0};
        int array_t[256]={0};
        // 利用match记录匹配上的字符数目
        int match = 0;
        int sum  = 0;
        int start = 0, len = 0;
        for(auto c:t) {
        	// 记录待匹配字符的数目
            if(array_t[c]==0) sum++;
            array_t[c]++;
        }
        while(r<s.length())
        {
            array_s[s[r]]++;
            // 字符出现次数相等,匹配上一个字符
            if(array_s[s[r]]== array_t[s[r]]) match++;
            r++;
            while(match == sum)
            {
                if(len==0 || r-l<len)
                {
                    start = l;
                    // r在之前已经+1, 这里的长度不需要加1
                    len = r-l;
                }
                array_s[s[l]]--;
                if(array_s[s[l]] < array_t[s[l]]) match--;
                l++;
            }
        }
        // 利用substr函数进行截取
        return len==0?"":s.substr(start,len);
}

这个算法的时间复杂度为O(2M+N), M,N分别是字符串S以及T的长度。while内部left与right最多都遍历到M,也就是O(2M).

二、找到字符串中所有字母异位词

题目链接 https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/
题目要求在字符串S中找到非空字符串P的字母异位词的字串,并返回字串的起始索引。

与最小覆盖字串思路一致,同样适用一个数组array_p记录字符串P中字符的出现次数,然后利用滑动窗口的思想找到一个满足array_p中所有字符次数的可行解,并优化它,然后移动继续寻找其他可行解,重复上述过程。
代码如下:

    vector<int> findAnagrams(string s, string p) {
        int l=0,r=0;
        vector<int> vc;
        // 只有小写字母
        int array_p[26]={0};
        int array_s[26]={0};
        // 使用sum统计需要考虑的字符个数
        int match = 0, sum = 0;
        for(auto c:p) 
        {
            if(array_p[c-'a']==0) sum++;
            array_p[c-'a']++;
        }
        while(r<s.length())
        {
            array_s[s[r]-'a']++;
            // 当某个字符出现次数满足时,匹配字符数+1
            if(array_s[s[r]-'a'] == array_p[s[r]-'a']) match++;
            r++;
            // 找到一个符合条件的窗口 然后优化
            while(match==sum)
            {
            	// 只有长度与p长度相等时 才会push
                if(r-l==p.size()) vc.push_back(l);
                array_s[s[l]-'a']--;
                if(array_p[s[l]-'a']>array_s[s[l]-'a']) match--;
                l++;
            }
            
        }
        return vc;

    }

三、 无重复字符的最长子串

题目链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
遇到字串问题,首先应该想到的就是滑动窗口技巧,类似之前的思路,使用一个数组记录字符出现的次数,当某个字符出现次数大于1时,收缩窗口直到该字符出现次数等于1。
值得注意的是,因为题目需要求解最长字串,因此应该收缩窗口得到可行解之后,才去更新答案。
这道题目与一般的滑动窗口解题不太一样,一般的滑动窗口是先找到一个解,当找到可行解时优化可行解,优化过程中更新答案。而本题是在通过内层的while循环找到一个解,然后通过内层while循环找到可行解,结束循环之后更新答案。

int lengthOfLongestSubstring(string s){
	int set[256] = {0};
	int left=0,right=0,ans=0;
	while(right<s.size())
	{
		char c = s[right];
		set[c]++;
		right++;
		
		while(set[c]>1)
		{
			set[s[left]]--;
			left++;
		}
		ans = max(ans, right-left);
	}
	return ans;
}
总结

通过上面三道题目,总结下滑动窗口思想:

int left = 0, right = 0;
while(right<s.size())
{
	window.add(s[right]);
	right++;
	while(valid){
		window.remove(s[left]);
		left++;
			
	}
}
}

一般来说window使用hash表或者数组记录即可。
稍微麻烦的是valid条件,很多复杂的代码都是为了实现这个条件的实时更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值