
该题使用滑动窗口的增量统计机制,
-
不重新扫描窗口
-
每次右边加一个字符、左边减一个字符,就更新一次计数
-
判断窗口是否满足条件用 O(1)
一开始我使用虽然也是滑动窗口,但是类似于暴力求解,时间复杂度为O(N^2):因为每次左指针每滑动一次时,检查是否满足覆盖条件仍然是O(N),所以整体下来就是O(N^2)。
cpp
class Solution {
public:
bool IsCover(string& s, int left, int right,unordered_map<char,int> charnum){
for(int i =left;i<=right;i++){
auto it=charnum.find(s[i]);
if(it!=charnum.end()){
if(charnum[s[i]]!=0) charnum[s[i]]--;
if(charnum[s[i]]==0) charnum.erase(it);
}
}
return charnum.empty();
}
string minWindow(string s, string t) {
unordered_map<char,int> charnum;
for(auto e: t) charnum[e]++;
int left=0; int right=0; int flag=0; int min_left=0; int min_right=0; int min=100000; int cur;
while(left<=right && right<s.size()){
if(IsCover(s,left,right,charnum)){
flag=1; cur=right-left+1;
if(cur<min){min_left=left; min_right=right; min=cur;}
left++;
}
else right++;
}
if(flag==0) return "";
else{
string s1(s.begin()+min_left,s.begin()+min_right+1);
return s1;
}
}
};
要使整体时间复杂度达到 O(N) ,必须采用滑动窗口的"增量统计"方式。核心思想是维护一个 count,表示当前窗口中还缺多少个字符才能完全覆盖字符串 t。
右指针扩张窗口时
-
当
s[r]是t中需要的字符(即哈希表need[s[r]] > 0)时:- 说明我们弥补了一个缺失字符 →
count--
- 说明我们弥补了一个缺失字符 →
-
不论
need[s[r]]是否大于 0,我们都执行need[s[r]]--,这是为了在窗口内部维持正确的剩余需求。
当 count == 0 时
说明当前窗口已经包含 t 需要的全部字符,可以开始尝试收缩左指针。
左指针收缩窗口时
-
在收缩前,先将
need[s[l]]++ -
如果
need[s[l]]增加后 大于 0 ,表示窗口因移除s[l]而缺少了该字符- →
count++(窗口不再满足覆盖条件)
- →
随后继续右移右指针,继续扩大窗口。
核心:need 是一个"差值计数器"
把 need 看成:
窗口中"缺少多少"这个字符(正数 = 缺,负数 = 多)
因此,随着窗口移动:
右指针进来一个字符 c
need[c]--
-
如果 need[c] > 0,说明这个字符正是我们需要的
→ count--,表示距离满足目标更近了
-
如果 need[c] <= 0,说明窗口中多出来了
→ 不影响 count
左指针移除一个字符 lc
need[lc]++
-
如果 need[lc] > 0,说明刚移走的是一个必须字符
→ 再次缺失了 → count++
-
如果 need[lc] <= 0,说明移走的是冗余字符
→ 不影响覆盖性
通过这种右 pointer 扩张、左 pointer 收缩的方式,我们可以保证每个字符最多被访问两次,因此整体时间复杂度为 O(N)。