难度困难
给定两个字符串 s
和 t
。返回 s
中包含 t
的所有字符的最短子字符串。如果 s
中不存在符合条件的子字符串,则返回空字符串 ""
。
如果 s
中存在多个符合条件的子字符串,返回任意一个。
注意: 对于 t
中重复字符,我们寻找的子字符串中该字符数量必须不少于 t
中该字符数量。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最短子字符串 "BANC" 包含了字符串 t 的所有字符 'A'、'B'、'C'
示例 2:
输入:s = "a", t = "a" 输出:"a"
示例 3:
输入:s = "a", t = "aa" 输出:"" 解释:t 中两个字符 'a' 均应包含在 s 的子串中,因此没有符合条件的子字符串,返回空字符串。
提示:
1 <= s.length, t.length <= 105
s
和t
由英文字母组成
进阶:你能设计一个在 o(n)
时间内解决此问题的算法吗?
思路:其实刷题多的朋友们应该能发现这就是一个滑动窗口的问题,我们可以每次都让右边界向右移动1个位置,然后考虑左边界,左边界对于位置的字符可能存在如下情况:
1.左边界位置对应的元素x根本不是t中的元素,此时左边界肯定是冗余的
2.左边界位置对应的元素x根本是t中的元素,要分如下情况讨论
A.假设当前窗口内该元素x的出现次数比需求量(t中x的出现次数)大,那么这个字符也是冗余的,可以剔除(左边界右移)
B.假设当前窗口内该元素x的出现次数不比需求量(t中x的出现次数)大,此时这个字符是不能删除的
现在最大的问题是,我们怎么知道当前滑动窗口内的元素满足了要求呢?
1.遍历字符串,统计出现次数,每一次判断都需要O(m)的复杂度
2.我们按照上面的思路会发现,实际上我们更新边界的策略会导致滑动窗口内的有效字符并不会减少,因此一旦我们发现当前区间内部满足有效字符的要求后,后续的所有情况都一定满足有效字符的要求。
上述的有效字符指的是当前滑动窗口内和t串匹配的字符,当然也要求数量相同,如s="aaa",t="aa",那么实际有效字符只有2个'a'而不是3个'a'。
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> hash_s, hash_t;
for(char c:t){
++ hash_t[c];
}
int left = 0, right = 0, cnt = 0;
string ans_s = "";
for(; right < s.size(); ++ right){
++ hash_s[s[right]]; //无论如何都要移动右边界
if(hash_s[s[right]] <= hash_t[s[right]]) ++ cnt;
while(left < right && hash_s[s[left]] > hash_t[s[left]]){//尝试将左边界移动
-- hash_s[s[left]];
++ left;
}
if(cnt == t.size()){
if(ans_s == "" || ans_s.size() > right - left + 1) ans_s = s.substr(left, right - left + 1);
}
}
return ans_s;
}
};