以下内容节选自公众号:labuladong《我写了套框架,把滑动窗口算法变成了默写题》,传送门在最下方参考链接1。
我觉得非常牛逼,在此记录,方便复习。
1、框架
2、LeetCode 76:最小覆盖子串
3、LeetCode 567:字符串排列
4、LeetCode 438:找到字符串中所有字母异位词
5、LeetCode 3:无重复字符的最长子串
6、LeetCode 30: 串联所有单词的子串
1、框架
以下框架中,遇到相关问题可改动三个地方:
(1)debug处可以删除。
(2)两处...
表示更新窗口数据的地方,它们的操作是完全对称的。
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) //source , target
{
unordered_map<char, int> need, window; //目标集,窗口集
for (auto c : t) //计算目标集
need[c]++;
int left = 0, right = 0, valid = 0; //valid用于指示当前窗口状态是否匹配目标集
while (right < s.length()) //计算的区间,左闭右开[left, right)
{
//c 是将移入窗口的字符
char c = s[right];
//移动右边界
right++;
//进行窗口内的数据的更新
...
/* debug输出位置*/
printf("window:[%d, %d)\n", left, right);
//判断窗口左侧是否需要收缩
while (window needs shrink)
{
//d 是将要移出窗口的字符
char d = s[left];
//移动左边界
left++;
//进行窗口内的数据的更新
...
}
}
}
2、LeetCode 76:最小覆盖子串
给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入:S = “ADOBECODEBANC”, T = “ABC”
输出:“BANC”
提示:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
思路:
- 算法要求O(n) 的时间复杂度,即只遍历一次字符串。
- 套用上面的框架,计算最短的窗口即可。
代码:
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char,int> need,window; //目标集,窗口集
int left=0,right=0,valid=0;
int start=0,min_len=INT_MAX; //存储结果
for(auto c:t)
need[c]++;
while(right<s.length())
{
char c=s[right++]; //移动右边界
if(need.count(c)>0) //记录窗口集
{
window[c]++;
if(window[c]==need[c])
valid++;
}
while(valid==need.size()) //窗口包含目标集,则进行优化
{
if(right-left<min_len) //更新结果
{
start=left;
min_len=right-left;
}
char d=s[left++]; //移动左边界
if(need.count(d)>0) //更新窗口集
{
if(window[d]==need[d])
valid--;
window[d]--;
}
}
}
return min_len==INT_MAX?"":s.substr(start,min_len);
}
};
结果:
3、LeetCode 567:字符串排列
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).
示例2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False
注意:
输入的字符串只包含小写字母
两个字符串的长度都在 [1, 10,000] 之间
代码:
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char,int> need,window;
for(auto c:s1)
need[c]++;
int left=0,right=0,valid=0;
while(right<s2.length())
{
char c=s2[right++];
if(need.count(c)>0)
{
window[c]++;
if(window[c]==need[c])
valid++;
}
while(right-left==s1.length())
{
if(valid==need.size())
{
return true;
}
char d=s2[left++];
if(need.count(d)>0)
{
if(window[d]==need[d])
valid--;
window[d]--;
}
}
}
return false;
}
};
结果:
4、LeetCode 438:找到字符串中所有字母异位词
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 1:
输入:
s: “cbaebabacd” p: “abc”
输出:
[0, 6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。
示例 2:
输入:
s: “abab” p: “ab”
输出:
[0, 1, 2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。
代码:
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char, int> need,window;
for(auto c:p)
need[c]++;
int left=0,right=0,valid=0;
vector<int> res;
while(right<s.length())
{
char c=s[right++];
if(need.count(c))
{
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++];
if(need.count(d))
{
if(window[d]==need[d])
valid--;
window[d]--;
}
}
}
return res;
}
};
5、LeetCode 3:无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
代码:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int> window;
int left=0,right=0,max_len=0;
char c;
while(right<s.length())
{
c=s[right++];
window[c]++;
while(window[c]>1)
{
char d=s[left++];
window[d]--;
}
max_len=max(max_len,right-left);
}
return max_len;
}
};
6、LeetCode 30: 串联所有单词的子串
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]
思路:
这道题的又不一样了,可以看到之前窗口边界 left 和 right 是以步长1增加,这次是以字典中的单词长度 m 为步长 增加。因此,我们可以在 [0,m)设置循环,作为起始点使用前面的滑动窗口框架。
另外注意,窗口集在每次循环中,要清空,去除上次循环的遗留信息。
代码:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> res;
int n=words.size();
if(n==0) //判断空集,以LeetCode的习性,测试用例中大多数会有空集
return res;
int m=words[0].size();
if(s.length()<n*m)
return res;
//框架开始
unordered_map<string,int> need,window;
for(int i=0;i<n;i++)
need[words[i]]++;
//循环,遍历起始位置
for(int i=0;i<m;i++)
{
window.clear(); //重要,清空窗口集
int left=i,right=i,valid=0;
while(right<s.length())
{
string c=s.substr(right,m);
right+=m;
if(need.count(c))
{
window[c]++;
if(window[c]==need[c])
valid++;
}
while(right-left==n*m)
{
if(valid==need.size())
res.push_back(left);
string d=s.substr(left,m);
left+=m;
if(need.count(d))
{
if(window[d]==need[d])
valid--;
window[d]--;
}
}
}
}
return res;
}
};
结果:
参考链接:
[1] labuladong:我写了套框架,把滑动窗口算法变成了默写题