目录
- 小结以及代码框架
- 76. 最小覆盖子串
- 滑动窗口
- 代码以及注释
- 567. 字符串的排列
- 滑动窗口
- 438. 找到字符串中所有字母异位词
- 3. 无重复字符的最长子串
- 化简框架
- reference
小结以及代码框架
滑动窗口技巧属于双指针技巧。
该算法的思路为维护一个窗口,不断滑动,然后更新答案。
大致框架如下:[参考labuladong的算法小抄]
int left = 0, right = 0;while(right < s.size())
{//增大窗口window.add(s[right]);right++;while(window needs shrink){//缩小窗口window.remove(s[left]);left++;}
}
主要的细节问题:
1、如何向窗口中添加新元素
2、如何缩小窗口
3、在窗口滑动的哪个阶段更新结果
//滑动窗口算法框架
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++;//进行窗口内数据更新(右移窗口)/*******************//**debug输出位置***/cout << "window:[ "<<left << " , " << right <<"]"<<endl;/******************///判断左侧窗口是否需要收缩while(window needs shrink){//d是将移出窗口的字符char d = s[left];//左移窗口left++;//进行窗口内数据的一系列更新(左移窗口)}}
}
76. 最小覆盖子串
https://leetcode-cn.com/problems/minimum-window-substring/
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
示例 2:
输入:s = “a”, t = “a”
输出:“a”
提示:
1 <= s.length, t.length <= 10^5
s 和 t 由英文字母组成
滑动窗口
1、初始化window和need两个哈希表,记录下窗口中的字符以及需要凑齐的字符:
unordered_map<char,int> need,window;
for(char c : t) need[c]++;
2、使用left和right变量初始化窗口两端,区间是左闭右开,初始情况下,窗口内是没有元素的。
int left = 0, right = 0;
while(right < s.size())
{//开始滑动
}
3、定义记录变量
valid_length表示window内满足need条件的字符个数,如果valid_length == need.size() 说明窗口已经满足了条件,已经完全覆盖了串T。
int valid_length;
在right向右扩充的时候,对新进来的元素进行检查,如果它是need中的元素,window中对应这个元素就要+1,然后判断window中这个元素的个数是不是need中这个元素的个数相同,如果相同说明这个元素在window中已经找完了,这时候就需要将valid_length+1了
4、套用模板
确定四个问题的具体细节:
1、当right移动,扩大window,应该更新哪些数据?
更新window计数器
2、当left移动,缩小window,应该更新哪些数据?
更新window计数器
3、什么条件下暂停扩大window,开始移动left缩小window?
当valid长度等于need大小时,应该收缩窗口
4、结果应该在扩大窗口时还是缩小窗口时进行?
缩小窗口的时候进行更新结果
代码以及注释
class Solution {
public:string minWindow(string s, string t) {unordered_map<char,int> window,need;//记录下t中有哪些元素以及每个元素的个数for(char c : t) need[c]++;//初始化窗口边界int right = 0, left = 0;//window满足need条件的元素个数int valid_length = 0;//结果字符串在s中的起始地址以及长度,这样就不要每次更新整个string了int start = 0 , length = INT_MAX;while(right < s.size()){//******************扩充窗口**************//扩充窗口right++;//扩充进来的新元素char c = s[right - 1]; //判断该元素是否是need中元素if(need.count(c)){window[c]++;if(window[c] == need[c]) valid_length++;}//*******************缩小窗口*************//如果need中所有元素都在window内,并且每个元素的频数也与window内元素频数相同,那么此时就得到了一个答案,记录答案,然后缩小窗口,以获得更优的答案while(valid_length == need.size()){//如果此次答案比上次的答案长度要小,我们才更新答案if(right - left < length){start = left;length = right - left;}//左边界向左移动left++;//d是刚移出窗口的元素char d = s[left - 1];//如果need中出现了dif(need.count(d)){//如果之前d元素在window和need出现的频数相同if(window[d] == need[d]){//删除了之后,频数不相同了,所以这个元素也就不满足了。valid_length--;}window[d]--;}}}//如果length没有更新,说明s不存在t中字符或者不满足字符频数,返回空字符串,否则,使用substr将子串返回return length == INT_MAX ? "" : s.substr(start,length);}
};
567. 字符串的排列
https://leetcode-cn.com/problems/permutation-in-string/
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).
示例2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False
注意:
输入的字符串只包含小写字母
两个字符串的长度都在 [1, 10,000] 之间
滑动窗口
精确化题目为:假设给你一个S和T,请问S中是否存在一个子串,包含T中所有字符且不包含其他字符
精确化本题细节:
1、移动left缩小窗口的时机:窗口大小大于t的大小,因为各种排列的长度应该是一样的。
2、当valid_length == need.size()时,说明窗口中的数据是一个合法的数据,应该立即返回true;
class Solution {
public:bool checkInclusion(string s1, string s2) {unordered_map<char,int> window,need;for(char c : s1) need[c]++;int left = 0,right = 0;int valid_length = 0;while(right < s2.size()){//扩充windowright++;char c = s2[right - 1];if(need.count(c)){window[c]++;if(window[c] == need[c]) valid_length++;}//缩小windowwhile(right - left >= s1.size()){if(valid_length == need.size()) return true;left++;char d = s2[left - 1];if(need.count(d)){if(window[d] == need[d]) valid_length--;window[d]--;}}}return false;}
};
438. 找到字符串中所有字母异位词
https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/
给定一个字符串 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) {if(s.empty()) return {};vector<int> result;unordered_map<char,int> need,window;for(char c : p) need[c]++;int left = 0,right = 0;int valid_length = 0;while(right < s.size()){//expand windowright++;char c = s[right - 1];if(need.count(c)){window[c]++;if(window[c] == need[c]){valid_length++;}}//shrink windowwhile(right - left >= p.size()){if(valid_length == need.size()){result.emplace_back(left);}left++;char d = s[left - 1];if(need.count(d)){if(window[d] == need[d]){valid_length--;}window[d]--;}}}return result;}
};
3. 无重复字符的最长子串
之前做过,这次用滑动窗口模板做:
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
示例 4:
输入: s = “”
输出: 0
提示:
0 <= s.length <= 5 * 10^4
s 由英文字母、数字、符号和空格组成.
化简框架
将need、valid去除,更新窗口数据只需要更新window计数器。
当window[c]大于1,说明窗口内存在重复字符,不符合条件,就应该移动left缩小窗口。
对于更新结果:更新结果的操作放在收缩窗口之后,因为收缩之后窗口不存在重复字符。
class Solution {
public:int lengthOfLongestSubstring(string s) {if(s.empty()) return 0;int res = 0;unordered_map<char,int> window;int left = 0, right = 0;while(right < s.size()){//expand windowright++;char c = s[right -1];window[c]++;//如果进入窗口的新元素在窗口内有重复元素,就需要移动left//shrink windowwhile(window[c] > 1){left++;window[s[left - 1]]--;}res = max(res,right - left);}return res;}
};
reference
《labuladong的算法小抄》