题目描述
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:
- 如果 S 中不存这样的子串,则返回空字符串 ""。
- 如果 S 中存在这样的子串,我们保证它是唯一的答案。
题目链接: https://leetcode-cn.com/problems/minimum-window-substring/
思路
使用滑动窗口来做,使用两个指针 left 和 right 来标记窗口的大小,窗口大小为 right-left+1。left 和 right 初始化为 0,首先将 right 指向的元素加入窗口中并增加 right,直到窗口中包含 T 中所有元素,然后通过增加 left 来缩小窗口大小。算法具体过程如下:
我们使用一个哈希表cnt
来记录 T 中元素出现的个数。那么,对于一个字符 c ,如果 c 在 T 中出现了,则 cnt[c]>0;如果 c 没有在 T 中出现,则 cnt[c]==0。使用 left 和 right 来遍历字符串 S,假设当前 right 指向的字符为 c ,将 cnt[c]--,如果 c 在 T 中出现,那么现在 cnt[c]>=0(之所以会大于 0 是因为 T 中可能会包含重复字符),否则 cnt[c]<0。如果减一后 cnt[c]>=0,说明我们在 S 中找到了一个在 T 中出现的字符,则当前找到的字符长度 curLen++。如果 curLen==T.length(),说明我们现在滑动窗口中已经包含了 T 中的所有字符,则可以通过增加 left 来缩小滑动窗口的大小。将 cnt[s[left]]++,如果 s[left] 是 T 中的字符,则加一后的 cnt[s[left]]>0,将 left+1 后,滑动窗口中就不完全包含 T 中的所有字符了(少了一个),这时停止缩小窗口的大小,也就是停止增大 left。如果加一后的 cnt[s[left]]<0,则说明 s[left] 没有在 T 中出现,可以继续缩小窗口。
循环结束的条件是 right>=S.length().
具体代码如下:
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> cnt;
for(auto c:t) cnt[c]++; // 统计 t 中字符出现的次数
int left=0, right=0;
int minLen=INT_MAX;
int curLen=0; // 当前在s中已经找到的字符个数
string ans="";
while(right<s.length()){
cnt[s[right]]--;
if(cnt[s[right]]>=0) curLen++;
while(curLen==t.length()){
int windowSize=right-left+1; // 窗口大小
if(windowSize<minLen){ // 找到了更短的符合要求的字符串
minLen=windowSize;
ans = s.substr(left, windowSize);
}
cnt[s[left]]++;
if(cnt[s[left]]>0) curLen--; // 窗口中的字符不全
left++;
}
right++;
}
return ans;
}
};
- 时间复杂度:O(n)
n 为 S 的长度。 - 空间复杂度:O(n)