76. 最小覆盖子串

186 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

题目描述

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

实例

输入: S = "ADOBECODEBANC", T = "ABC" 输出: "BANC"

说明

如果 S 中不存这样的子串,则返回空字符串 ""。 如果 S 中存在这样的子串,我们保证它是唯一的答案。

思路

对于这题目,一开始我只能想到一个暴力求解法(暴力大法好),其实自己想一下就很清楚他的时间复杂度为O(s^3+t) (S是字符串的长度 T是字符串的长度)所以oj会崩的.这个暴力求解就不行. 我看了leetcode中一个大佬的题解,就学到了有一种方法,现在分享给你. 这个大佬是labuladong,这个大佬讲的特别清楚,他有一个微信公众号,可以关注一波(绝对没有收钱纯良心推荐) 这是一个模板,专门解决子串、子数组问题,这是滑动窗口的算法.这个理解很简单,但是各种细节问题很多。比如说如何向窗口中添加新元素,如何缩小窗口,在窗口滑动的哪个阶段更新结果。即便你明白了这些细节,也容易出 bug,找 bug 还不知道怎么找,真的挺让人心烦的。我深受其害.好好学! 首先简单讲一下滑动窗口的一个算法的理解.

  1. 既然叫窗口,肯定有他的道理.在一个字符串S上建立一个窗口,框住你想要框住的字符,如果用数学话讲的话就是区间.这是一个左闭右开的区间[).
  2. 有了窗口,那么怎么框呢?用双指针中的左右指针的技巧.我们开始初始化left=0,right=0,left代表左边的字符,right代表右边的字符.这就可以表示一个区间了.
  3. 我们不断使right++增大他的窗口,知道所有的T字符串的字符全在窗口内.
  4. 那么就要找最优的解!所以我们要使left++减小窗口,达到题目中的最小子串.
  5. 我们需要遍历完,防止有遗漏.

简单说一下,就是先找可行解(最长的解),找到后,缩小,得到最优解(最小的解). 这是大佬的解释对滑动窗口:左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动,这就是「滑动窗口」这个名字的来历。 如果你明白了我上述所说的话,那么你就掌握了滑动窗口的核心思维. 事先说明一下,我比较懒,也菜不会画图,所以以下引用大佬的图片帮助你理解原理. labuladong 一开始的left和right都为0,接下来我们就要增大right把T中的全框在里面.labuladong 框住之后,我们就要找最优解,所以将left增大减小窗口. labuladong 如果减小窗口,直到T无法成为窗口内的子串就停下来. 在这里插入图片描述 这样的步骤一直持续下去,直到尾部.

代码

class Solution {
public:
    string minWindow(string s, string t) {
        /* 滑动窗口算法框架 */
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;//这就是个循环,统计T字符个数
    
    int left = 0, right = 0;
    int valid = 0; 
    int start = 0, len = INT_MAX;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        if(need.count(c))
        {
            window[c]++;
            if(window[c]==need[c])
            valid++;

        }

        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);//检查你的窗口是否正确
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (valid==need.size()) {
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            if(need.count(d))
            {
                if(need[d]==window[d])
                valid--;
                window[d]--;
            }
        }
    }
    return len==INT_MAX?"":s.substr(start,len);
}

    
};

我看过一些评论,向你们解释一些小代码的原因:

  1. valid这个变量,我理解是存储T子串中不同字符的个数,在need中,所以对于valid==need.size(),简单解释一下,你可能会想可不可以改成t.size()这是不行的,如果你改了,对于T字符串中重复的字符就不好处理了.
  2. 至于need.count(d)这句话,就是碰到了T中的字符,进行更新和存储.

另外向大家告诉一个小知识,如果你想看源代码,就是主函数的话 点击图中的小图标就可以看到.me 如果你觉得掌握了,可以做以下题目: 找到字符串中所有字母异位词 无重复字符的最长子串 字符串的排列

如果你还不理解,可以看leetcode题解,会有一篇适合你的.

推荐

我真心推荐labuladong这个大佬的题解,确实很好很清晰,但是这个题解嘛各有各的看法,如果觉得好,符号自己的学习的话关注一波,还有一个就是负雪明烛这个大佬,他的题解也很详细的,而且他一个微信群,可以讨论很多代码方面的问题.这些都是我学习的对象.