窗口算法
窗口算法是我自己起的不严谨的名字,因为最近做了几个leetcode中几个算法题目,发现其算法的都需要通过维护一个窗口来实现,说到窗口,我们肯定会想到TCP/IP中的滑动窗口协议,其实这类算法题目和这个有点神似的。
滑动窗口协议是用来改善吞吐量的一种技术,TCP中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。——百度百科
现在我汇总一下,分析一下这种类型题目的思路。
最长无重复字符的子串
该题目原文是:“Given a string, find the length of the longest substring without repeating characters.”
思路很简单,维护一个窗口,窗口的大小表示当前的无重复的字符串的长度,用一个变量来记录窗口的起始位置,遍历字符串的字符,遇到一个字符,就需要判断该字符串是否出现过
- 没出现过,那么直接记录该字符在字符串中索引
- 出现过,判断上一次该字符出现的位置;该位置加1后与窗口的起始位置比较,取两者中的最大值作为新窗口的起始位置。
然后每次计算现在窗口的大小,不断更新窗口的历史最大值。代码如下:
int lengthOfLongestSubstring(string s)
{
vector<int> charIndex(256, -1);
int longest = 0, m = 0;
for (int i = 0; i < s.length(); i++) {
m = max(charIndex[s[i]] + 1, m); // 确定窗口的起始位置
charIndex[s[i]] = i;
longest = max(longest, i - m + 1);
}
return longest;
}
最小窗口子串
题目原文是:“Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).”就是要求S中包含T的所有的字符的最短子串的长度。
思路的核心也是窗口,把所有包含T中字符的窗口(子串)大小求出来,取其中的最小值就可以。如何求得窗口范围,这个题目稍微麻烦了些。
我们需要把T中出现的字符存到一个hashmap中(C++11提供了unordered_map),以便我们快速访问。由于此题目的特殊性都是字符,我们可以提供一个大小为256的数组来充当hashmap.
- 然后从S的字符串的开头开始遍历,尾指针不断往后扫,遇到一个字符,判断是否在T中出现过,若出席过,count++,直到count==T.size().
- 这时候然收缩头指针,直到不能再收缩为止,此时找到该段包含T中所有字符的最小窗口。之后继续尾指针继续后扫,遍历S串的下一个字符,重复以上的步骤,直到结束。过程中保存历史最小窗口。
代码参考了一下网上的,如下:
string minWindow(string S, string T) {
int sLen=S.size();
int tLen=T.size();
if(tLen==0 || sLen<tLen) return "";
int needFind[256]={0};
int hasFind[256]={0};
for(int i=0;i<tLen;i++)
{
needFind[T[i]]++;
}
int minWindowLength=INT_MAX;
int minBegin=0;
int minEnd=sLen-1;
int begin=0;
int end=0;
for(int count=0;end<sLen;end++)
{
//T中没有出现的字符
if(needFind[S[end]]==0) continue;
hasFind[S[end]]++;
//若是超过了T中字符出现的次数,就不需要count++
if(hasFind[S[end]] <= needFind[S[end]])
count++;
//a window exists from begin to end
if(count==tLen)
{
//不断移动头指针来找到最小窗口
while(begin<end)
{
if(needFind[S[begin]]==0)
{
begin++;
continue;
}
if(hasFind[S[begin]] > needFind[S[begin]])
{
hasFind[S[begin]]--;
begin++;
continue;
}
else
break;
}
int tmpWindowLength=end-begin+1;
if(tmpWindowLength < minWindowLength)
{
minBegin=begin;
minEnd=end;
minWindowLength=tmpWindowLength;
}
}
}
if(minWindowLength==INT_MAX)
return "";
return S.substr(minBegin,minWindowLength);
}
我记得还有一个类似的题目,但是想不起来了。
总的来说这类题目需要我们维护一个窗口,可以通过记录窗口的大小,头指针,尾指针来维护,而且一般窗口中包含的元素需要满足题意中某些条件,一般来说就是包含了某某的全部的元素。
这类题目不难,关键在于想到如何来不断调整窗口的大小,头尾指针,想明白了这点,问题就基本可以解决哈。