一、引言
什么是滑动窗口?
滑动窗口其实就是快慢双指针的一个应用。在两个指针中间的部分就是一个窗口,我们动的是左右指针,让窗口动态的扩大或者缩小,这样就实现了滑动。
首先开始看代码的框架
auto left = 0 ,right = 0;
auto window;
while(right < rightboundry)
{
//在满足条件的情况下扩大窗口
window.add(right->val);
right++;
//在满足条件的情况下收缩窗口
while(winodw need shrink)
{
window.pop(left->val);
left++;
}
}
东哥给的代码:
// 滑动窗口算法伪码框架
void slidingWindow(string s)
{
// 用合适的数据结构记录窗口中的数据,根据具体场景变通
// 比如说,我想记录窗口中元素出现的次数,就用 map
// 如果我想记录窗口中的元素和,就可以只用一个 int
auto window = ...
int left = 0, right = 0;
while (right < s.size())
{
// c 是将移入窗口的字符
char c = s[right];
window.add(c);
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...
// *** debug 输出的位置 ***
printf("window: [%d, %d)\n", left, right);
// 注意在最终的解法代码中不要 print
// 因为 IO 操作很耗时,可能导致超时
// 判断左侧窗口是否要收缩
while (window needs shrink)
{
// d 是将移出窗口的字符
char d = s[left];
window.remove(d);
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
所以必备的东西:
1.左右指针,左指针负责缩小窗口,右指针负责扩大窗口
2.拿什么东西记录窗口的值,判断是否要拿进窗口
或者是记录窗口中特殊的东西
所以这自然而然引出下面的问题
1.什么时候、什么条件需要扩大窗口?
2.什么时候、什么条件需要缩小窗口?
3.什么时候、什么条件更新答案?
我觉得回答了下面是哪个问题基本上这题就解决了。
二、直接看题
这题确实很难,但是我们有办法
看到这种子串问题第一想法就是滑动窗口的!
我们还是把窗口的代码拿来
auto left = 0 ,right = 0;
auto window;
while(right < rightboundry)
{
//在满足条件的情况下扩大窗口
window.add(right->val);
right++;
//在满足条件的情况下收缩窗口
while(winodw need shrink)
{
window.pop(left->val);
left++;
}
}
先看什么时候扩张窗口?
感觉可以一直扩张。这个问题先到这
那什么时候收缩窗口呢?
是不是在目前的窗口里已经包含了t的全部元素了?
或者说等我扩张到包含t的全部元素后就可以收缩窗口了?
(那么这里自然就需要两个东西,一个是保存t的信息,一个是保存窗口里的信息,作为条件变量看是否和t一致,如果一致ok停止扩张开始收缩)
那么什么时候更新答案?
我们找的是最小,是不是每次收缩的时候更新一次比较好啊?看看和上一次比是不是既收缩了又保证了满足要求?
那什么时候停止收缩呢?
应该是不满足t的要求了就可以扩张了,因为上一轮收缩我已经找到一个最小的子串了。
okk那我们开始扩张代码
int left = 0 ,right = 0;
unordered_map<char,int>need, window;
for(char c : t)
{
need[c]++;
}
首先这里先把必要的东西拿出来,左右指针,t的信息,窗口内的信息都创建好,然后收集t的信息
int left = 0 ,right = 0;
unordered_map<char,int>need, window;
for(char c : t)
{
need[c]++;
}
while(right < s.size())
{
//在满足条件的情况下扩大窗口
char c = s[right];
if(need.count(c))
{
window[c]++;//window.add(right->val);
}
right++;
//在满足条件的情况下收缩窗口
while(winodw need shrink)
{
window.pop(left->val);
left++;
}
}
这里window里保存的是t中出现的元素的个数,也就是我们要找的元素。
这里应该需要考虑,就是t中的元素可能出现不止一次。所以其实首先该判断的是该元素是否是我们要找的元素,然后再统计次数!
就是还有个东西是,比如t中c出现了3次,我们每次在s中滑动窗口的时候,每次c出现一次,我们window里的c计数都加一次,只有当window里c出现3次的时候也就是和t一样,我们才表明,ok这个c出现完了。
int left = 0 ,right = 0;
unordered_map<char,int>need, window;
for(char c : t)
{
need[c]++;
}
int valid = 0;
while(right < s.size())
{
//在满足条件的情况下扩大窗口
char c = s[right];
if(need.count(c))
{
window[c]++;//window.add(right->val);
if(window[c)==need[c]) valid++;
}
right++;
//在满足条件的情况下收缩窗口
while(winodw need shrink)
{
window.pop(left->val);
left++;
}
}
那么这样我们的扩张策略就写完了
再看收缩策略,根据前面的,每次每个我们要找的元素都出现了他们该有的次数后我们就可以收缩窗口了。
int left = 0 ,right = 0;
unordered_map<char,int>need, window;
for(char c : t)
{
need[c]++;
}
int valid = 0;
while(right < s.size())
{
//在满足条件的情况下扩大窗口
char c = s[right];
if(need.count(c))
{
window[c]++;//window.add(right->val);
if(window[c)==need[c]) valid++;
}
right++;
//在满足条件的情况下收缩窗口
while(valid==need.size())
{
//更新答案
char k = s[left]
if(need[c])
{
if(window[k]==need[k] valid--;
window[k]--
}
left++;
}
}
收缩条件和前面一样的。
如果说左边这个字符使我们要找的
应该先判断他是不是出现了他该出现的次数,如果是那么valid--,表示删除了就不满足啦
然后才是window--
然后再收缩
这题还有个就是返回的是最小子串,你如果每次都在这里构建子串的话内存会超。
substr()的参数是构建的起始位置和长度。
所以更新这两个参数就行
然后在最后再更新子串。
完整代码:
#include <iostream>
#include <string>
#include <unordered_map>
#include <climits> // 包含 INT_MAX 常量
using namespace std;
class Solution {
public:
string minWindow(string s, string t) {
int left = 0, right = 0;
unordered_map<char, int> need, window;
// 初始化need
for (char c : t) need[c]++;
int tar = need.size();
int valid = 0;
int len = INT_MAX;
int start = 0;
while (right < s.size())
{
char c = s[right];
if (need.count(c))
{
window[c]++;
if (window[c] == need[c]) valid++;
}
right++;
while (valid == tar) {
if (right - left < len) {
len = right - left;
start = left;
}
char k = s[left];
if (need.count(k)) {
if (window[k] == need[k]) valid--;
window[k]--;
}
left++;
}
}
return len == INT_MAX ? "" : s.substr(start, len);
}
};
那么本题就结束。