题目来源
题目描述
class Solution {
public:
string minWindow(string s, string t) {
}
};
题目解析
滑动窗口 + 欠账表
思想:
- 在滑动窗口类型的问题中都会有两个指针,一个用于[延伸]现有窗口的
j
指针,一个用于[收缩]窗口的i
指针 - 在任何时刻,都只有一个指针运行,而另一个保持静止
- 我们在s上滑动窗口,通过移动r指针不断扩张窗口,当窗口包含t全部所需的字符后,如果能够收缩,我们就收缩窗口直到得到最小窗口(j - i + 1)
步骤:
- 不断增加j使滑动窗口增大,直到窗口包含了T的所有元素
- 不断增加i使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度,并保存最小值
- 让i再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到j超出了字符串S范围。
问题是:如果判断滑动窗口中包含了T的元素
- 通过维护一个need:
- 步骤一:我们用一个字典need来表示当前滑动窗口中需要的各元素的数量
- 步骤二:一开始滑动窗口为空,用T中各元素来初始化这个need
- 步骤三:当滑动窗口扩展或者收缩的时候,去维护这个need字典,例如当滑动窗口包含某个元素,我们就让need中这个元素的数量减1,代表所需元素减少了1个;当滑动窗口移除某个元素,就让need中这个元素的数量加1。
- 也就是说:
- need始终记录着当前滑动窗口下,我们还需要的元素数量。
- 我们在改变i、j的时候,需要同步维护need
- 注意:
- need可以为负数,如果某个元素存储的是负数代表这个元素是多余的。比如当need等于{‘A’:-2,‘C’:1}时,表示当前滑动窗口中,我们有2个A是多余的,同时还需要1个C。
- 这么做的目的就是为了步骤二中,排除不必要的元素,数量为负的就是不必要的元素,而数量为0表示刚刚好。
- 综上:当need中所有元素的数量都小于等于0时,表示当前滑动窗口不再需要任何元素
优化:
- 如果每次判断滑动窗口是否包含了T的所有元素,都去遍历need看是否所有元素数量都小于等于0,这个会耗费O(k)的时间复杂度
- 其实这个是可以避免的,我们可以维护一个额外的变量needCnt来记录所需元素的总数量,当我们碰到一个所需元素c,不仅need[c]的数量减少1,同时needCnt也要减少1,这样我们通过needCnt就可以知道是否满足条件,而无需遍历字典了。
- 什么叫做所需元素?只有need[c]>0大于0时,代表c就是所需元素
class Solution {
public:
string minWindow(string s, string t) {
std::vector<int> need(128, 0);
for(char c : t){
need[c]++;
}
int count = t.size();
int i = 0, j = 0, start = 0, size = INT32_MAX;
while (j < s.size()){
char c = s[j];
//先把右边的字符加入窗口
--need[c];
if(need[c] >= 0){
--count; //是不是有效还钱
}
// 每加入一个字符都判断一下窗口内是不是已经包含了全部字符了
if(count == 0){
//如果是的,那么尝试缩小窗口(增加i,排除多余元素)
while (i < j && need[s[i]] < 0){// l == r时不形成窗口&&多余的
need[s[i++]]++;//将多还的弹出窗口
}
//此时窗口符合要求, 更新答案
if(j - i + 1 < size){
size = j - i + 1;
start = i;
}
// i增加一个位置,寻找新的满足条件滑动窗口
need[s[i]]++;
count += 1 ; //由于 移动前i这个位置 一定是所需的字母,因此count才需要+1
i++;
}
j++;
}
return size == INT32_MAX? "" : s.substr(start, size);
}
};
类似题目
题目 | 思路 |
---|---|
leetcode:76. 最小子串:字符串s2覆盖s1的所有子串的最小子串 Minimum Window Substring | 滑动窗口 + 欠钱表 |
leetcode:567. 是否包含:字符串s2是否包含s1的排列 Permutation in String | 滑动窗口 + 欠账表 |
leetcode:209. 最小子数组长度:无序正数数组其和>=k的长度最小的子数组的长度 Minimum Size Subarray Sum | 滑动窗口;前缀和 + 二分 |
leetcode:560. 所有子数组个数:无序整数数组其和 == K 的子数组的个数 Subarray Sum Equals K | 主要思路:计算完包括了当前数前缀和之后,我们去查一查在当前数之前,有多少个前缀和等于preSum - k呢? |
leetcode:718. 最长子数组长度:两个数组公共的,最长的子数组长度 Maximum Length of Repeated Subarray | |
leetcode:632. 最小区间:覆盖K个列表元素的最小区间 Smallest Range Covering Elements from K Lists | |
leetcode:239. 滑动窗口最大值 Sliding Window Maximum | 💖💖💖💖💖 |
leetcode:30. 串联所有单词的子串 Substring with Concatenation of All Words | hard级别,暂时不做 |
leetcode:727. 最小窗口序列Minimum Window Subsequence | |
leetcode:159.最多有两个不同字符的最长子串 | 滑动窗口 |
leetcode:904. 水果成篮 | 滑动窗口 |