76. Minimum Window Substring

题目链接

76. Minimum Window Substring

题目描述

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

示例 2:
输入:s = "a", t = "a"
输出:"a"

提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成

思路

用滑动窗口解决,维护一个左指针和一个右指针。

代码及解析

class Solution {
public:
    string minWindow(string s, string t) {
        map<char, int> needs;
        //needs表示当前在s串中需要遍历到的某个字符的数量,
        //当needs['a']>0的时候,表示还期待往右遍历到a字符
        //当needs['a']=0的时候,表示目前在s串中遇到的a字符的数量已经和t串中a字符的数量相等了
        //当needs['a']<0的时候,表示目前在s串中遇到的a字符的数量已经比t串中a字符的数量还要多了
        map<char, bool> flag;
        for (auto e : t) {
            needs[e]++;
            flag[e] = true;
        }

        int l = 0, r = 0, min_l = 0, min_size = INT_MAX, cnt = 0;
        //cnt表示当前s串中已经满足t串中字符的数目,
        //当cnt==t.size()的时候表示s串中已经满足t串的所有字符了
        for (; r < s.size(); r++) {
            //满足t串中的一个字符,计数器加一,
            //注意,只有needs[s[r]]减完1之后还大于或者等于0,才表示这次在s中遇到的s[r]字符是有效的
            //比如t串只有2个a字符,当在s中遇到第3个a字符的时候,我们只需要让--needs['a'],
            //此时needs['a'] = -1,表示当前在s中遇到的a字符数目已经多了一个 
            if (--needs[s[r]] >= 0) {
                cnt++;
            }

            while (cnt == t.size()) {
                //当前s串[l, r]之间的字符已经满足t了,但是可能不是最短的,
                //意味着这段区间可能要么有些字符是t中不需要的,
                //要么是对应的needs小于0,即相应的字符出现次数太多了
                //那么为了达到最短的要求,就需要不断把左边界往右移动,即收缩边界
                //移动之前首先判断当前[l,r]这个字串的长度是否是小于之前的最小长度min_size
                //由于最开始min_size设置为了INT_MAX,所以当第一次走到这里的时候是一定满足条件的
                if (r - l + 1 < min_size) {
                    min_l = l;//更新最小左边界
                    min_size = r - l + 1;//更新min_size
                }

                if (flag[s[l]] && ++needs[s[l]] > 0) {
                    //这段代码比较关键
                    //如果当前l位置的字符是t中出现过的,那么由于后面左边界会向右移动
                    //那么会出现两种情况:
                    //1.假设之前needs[s[l]]刚好等于0,那么当++needs[s[l]]之后就大于0,说明去掉当前字符已经不满足t串中s[l]字符的数量了,也就意味着我们满足t串字符数目要减一。
                    //2.设之前needs[s[l]]小于0,那么当++needs[s[l]]之后就还是小于或者等于0,这时候不需要更新cnt
                    //其实这段逻辑就是和上面的代码相对应的,只不过这里还有一种情况,就是当前l处的字符根本不是t中需要的,这种情况,只需要把l++即可。
                    /*
                    if(--needs[s[r]] >= 0){
                        cnt++;
                    }
                    */
                    cnt--;
                }

                //注意,我们这里是一定会收缩左边界的,问题来了,假设我们现在[l,r]之间的字符串刚好是我们目标字符串,那么l向右收缩之后,不是不符合条件了吗?那怎么记录这个位置的左边界和有边界呢?
                //没错,这个就是通过min_l和min_size记录的,所以我们左边界和右边界都是在往右移动,只不过我们根据条件来更新min_l和min_size。
                l++;//收缩左边界
            }
        }

        return min_size > s.size() ? "" : s.substr(min_l, min_size);
    }
};