leetcode面试经典150题——33 最小覆盖子串(滑动窗口)

题目: 最小覆盖子串

描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。

leetcode链接

方法一:滑动窗口
题目解读:该题与32题(详细见上一篇讲解32串联所有单词的子串)很相似,唯一不同的是本题所求子串中不仅包含t中的所有字符,还可以包含其他的字符,因此所求子串和t是一个包含的关系,而32题所求子串和t的字符是一一对应的,没有多余的字符,即子串的长度等于t的长度,而此题子串的长度大于等于t的长度。
求解:同样的我们利用32题的滑动窗口的方法,过程完全一致,唯一不同该题的窗口为动态长度的,即定义一个窗口,初始长度为0,然后判断当前窗口是否包含t中所有的字符,如果包含,那么记录该窗口的长度并且维护一个最小的满足条件的窗口长度,并且left指针右移,即缩小窗口的长度。如果不包含t中所有字符,那么right指针右移,即增大窗口,循序以上操作。
那么我们如何判断窗口是否包含t中的所有字符呢,我们考虑最佳时间复杂度的做法,即为定义2个unordered_map,分别为map1和map2,map1记录中字母的出现次数,map2记录窗口字母的出现次数,然后判断map1和map2,如果map1中所有的关键字的值都小于等于map2,那么可以认为窗口包含了t中所有字符。这里在存储map2的时候,只需要存储在map1中出现过的关键字,含义即为只存储t中出现过的字符到map中,这样做节省了空间的消耗。
时间复杂度:o(Cn,+m) 时间包括指针的移动(窗口的枚举),为o(n),和判断窗口是否包含t中字符的时间,即为哈希表的查询,设哈希表的大小为C,那么时间复杂度为o(Cn),还有初始把t中所有字符插入哈希表的时间,为o(m),所以总时间复杂度为o(Cn+m)。

过程如图所示,阴影部分为当前窗口,其中红色字体的字母为存储在哈希表中的元素,窗口内其它元素不进行存储
在这里插入图片描述

发现此时窗口不包含t中的所有元素,那么right指针右移,此时窗口包含t中所有的元素,记录此时的子串长度
在这里插入图片描述
然后left指针右移
在这里插入图片描述

class Solution {
public:
    unordered_map<char,int> smap,tmap;

    string minWindow(string s, string t) {
        int n = s.size(),m = t.size();
        int len = INT_MAX, ansL = -1, ansR = -1;
        for(auto &c : t){
            tmap[c]++;//存储t中单词出现的次数
        }
        int left = 0,right = -1;
        while(right<n){
        	//窗口右移
            if(tmap.find(s[++right])!=tmap.end()){//统计在t中出现过的字符的个数
                smap[s[right]]++;
            }
            while(check()&&left<=right){//如果窗口中包含s所有字符,那么left指针右边移
                if (right - left + 1 < len) {
                    len = right - left + 1;
                    ansL = left;
                }
                if(tmap.find(s[left])!=tmap.end()){
                    smap[s[left]]--;
                }
                left++;
            }
        }
        return ansL == -1 ? string() : s.substr(ansL, len);
    }
    bool check(){
        for(auto &p:tmap){
            if(smap[p.first]<p.second){
                return false;
            }
        }
        return true;
    }
};
  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
滑动窗口是一种常用的算法技巧,可以用于解决一类问,其中包括一些LeetCode上的目。通过维护一个窗口,我们可以在线性时间内解决一些需要处理连续数组或字符的问。以下是一些常见的滑动窗口: 1. 最小覆盖(Minimum Window Substring):给定一个字符S和一个字符T,在S中找出包含T所有字符的最小。 2. 字符的排列(Permutation in String):给定两个字符s1和s2,判断s2是否包含s1的排列。 3. 找到字符中所有字母异位词(Find All Anagrams in a String):给定一个字符s和一个非空字符p,找到s中所有是p的字母异位词的。 4. 替换后的最长重复字符(Longest Repeating Character Replacement):给定一个只包含大写英文字母的字符s,你可以将一个字母替换成任意其他字母,使得包含重复字母的最长的长度最大化。 5. 至多包含两个不同字符的最长(Longest Substring with At Most Two Distinct Characters):给定一个字符s,找出至多包含两个不同字符的最长的长度。 以上只是几个例滑动窗口可以应用于更多类型的问。在解决这些问时,我们通常使用两个指针来表示窗口的左右边界,并根据具体问的要求移动窗口。在每次移动窗口时,我们可以更新窗口的状态,例如统计字符出现次数、判断窗口是否满足条件等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值