一:题目
二:思路
思路拿别人的,感觉写的很nice!! 渣渣杰只能膜拜大佬的了
1.滑动窗口的思想:
left 指针和 ring 指针,保证两个指针之间的字符串包含所需要的全部字符。
2在保证 1 的前提下,
向右移动 left,保证 left 的位置为所需要的字符,这样就实现了最短子串。
例如 :s = ABKKKIWI
t = KIWI
left 应该指向 4 而不是 0、1、2 或 3
因为要返回子串,所以我们不直接记录长度,而是使用 min_left 和 min_right 记录最小窗口的位置。
3.关键问题1:怎么确保当前窗口包含所有需要的字符?
可以使用哈希表来记录,key = 需要的 t 中的所有字符,value = 所需字符的个数
例如:s = ABKKKIWI t = KIWI
t_map[‘K’] = 1;
t_map[‘I’] = 2;
t_map[‘w’] = 1;
当向右移动 right 指针时,只要碰到一个 t 中的字符时,对应的哈希表计数 t_map[s[right]] 就减 1
例如:上面的例子,当 right 指针移动到 7 时,哈希表会变为
t_map[‘K’] = -2;
t_map[‘I’] = 0;
t_map[‘w’] = 0;
当所有哈希表中的计数值都不大于 0 时,说明滑动窗口中已经包含了所有需要的字符串。(等于 0 说明窗口中刚好包含所需要的字符 刚好 2 个 I,小于 0 说明窗口中有多余的 t 中的字符 有多余的 K)
但是判断哈希表全部计数值为 0,并不能在 O(1) 时间内实现(需要遍历),因此我们可以使用一个变量 t_len 来记录遍历过的数组,代表还需要找多少个字符。
right 指针向右移动时,当碰到哈希表中存在的字符,且哈希表中的计数值大于0时,说明这种字符还没找够,此时 t_len -=1,right 进行向右移动。如果碰到的字符对应的计数值已经等于0或者小于0,那么 t_len 不能减 1,因为这种字符已经多余了,但哈希表中的计数要接着减。
当 t_len = 0 时,说明窗口内已经包含全部所需要的字符了,接下来只需要移动 left 指针,使得窗口最小即可。
4:关键问题2:怎么把 left 移动到正确位置?
left 正确位置:使得窗口开头就是需要的字符,且不多余。
例如:s = ABKKKIWIWK t = KIWI 当 right 指针移动到 7 时,哈希表会变为
t_map[‘K’] = -2;
t_map[‘I’] = 0;
t_map[‘w’] = 0;
此时的 left 还指向 0, 我们要缩小窗口,AB很容易,不在哈希表中,直接跳过即可,此时哈希表中的 K 计数小于 0 说明存在多余的 K,我们边缩小窗口(即 left ++) 边增加哈希表中的计数,知道计数为0,说明刚好找到了最后一个K(再缩小就没有 K 了),此时窗口为可能的最小窗口,计算该窗口的长度,比较,更新记录即可。
然后 left++ 跳过这个这个 K,使得窗口继续向右滑动寻找下一个 K,寻找下一个子串。
三:上码(一点也不难也就写了5个小时而已)
class Solution {
public:/**思路:1.滑动窗口类型的题2.滑动窗口的三要素滑动窗口的起始位置 字符串的起始位置滑动窗口的滑动体 在s中包含t的字符串滑动窗口的结束位置 当在s中t中的字符全部都遇到了 那么此位置为结束位置 */string minWindow(string s, string t) {map<char,int> m;//统计t中各个字符的个数,为了可以确保在找到的字符串中 包含t中的所有元素for(int i = 0; i < t.size(); i++) {m[t[i]]++;}int left = 0;int minLeft = 0;int minRight = s.size();int len = t.size();//用于判断找到的字符串中是否包含t的全部元素for(int right = 0; right < s.size(); right++) {//这里表示的是s中的字符在m中,m.find(s[right]) != m.end() 表示存在 if(m.find(s[right]) != m.end()){if(m[s[right]] > 0){len--;//找到一个元素那么t的长度减一 }m[s[right]]--;//这里面m[s[right]] 可能会出现负数 因为s中可能会出现多个相同的和t中相关的字符, }//说明我们在s找到了一个字符串包含t中的全部字符if(len == 0){//开始移动左指针,移到包含t中字符的 最小位置为止while(m.find(s[left]) == m.end() || m[s[left]] < 0){ if(m.find(s[left]) == m.end()){left++;//说明s中刚开始的字符没在找到的包含t中字符的字符串中}else{m[s[left]]++;left++;//有可能这个字符的在s中有重复的t中的字符,所以我们需要将其加到等于0为止,这是left和right之间包含t中字符的最小个数 }}//更新最小窗口的if(right-left < minRight - minLeft){minLeft = left;minRight =right;}//下一个窗口 也就是包含t中所有字符的另一个字符串m[s[left]]++;//恢复s[left]字符的个数left++;//淘汰找到包含t的字符串中最左边的字符,准备拥抱新的字符串包含t的所有元素 len++;//表示目前的字符串中还差一个字符就可以包含t中的所有字符(为下有一次遍历做准备)}}if(minRight == s.size()){return "";}else{return s.substr(minLeft,minRight-minLeft+1);}}
};
这个题是今年秋招 字节一面中半个小时需要AC出来的一到原题,我用了5个小时 哈哈哈哈哈哈哈哈 我也就能无奈的笑笑
自己做了3个小时 没做出来 看大佬思路 又学习人家的码 又用了俩小时 菜鸡杰 菜鸡杰 这回菜到家了 。 本来想放弃 但是如果遇到难的就放弃,那就不用学了,越积越多,虽然花的时间长,但是慢慢来 一切都会好的
认怂 复盘 慢慢来 加油 素未谋面的你 晚安!