美文网首页
以leetcode 76. 最小覆盖子串为例,学习滑动窗口的技巧

以leetcode 76. 最小覆盖子串为例,学习滑动窗口的技巧

作者: Burlong | 来源:发表于2022-03-21 17:34 被阅读0次

leetcode 76. 最小覆盖子串

用滑动窗口来解答此题,有几个关键点:

一、确认整个遍历操作中我们需要用到的变量

  • left、right 就不用说了,在滑动窗口类的题目这两个变量基本必有;
  • needs 哈希桶,用来表示目标字符串的各个字符以及各个字符要求出现的次数;
  • windows 哈希桶,用来表示当前窗口中符合的字符,以及字符出现次数;
  • start、len 分别用来实时标记最小子串的开始索引,以及其长度;
  • validCount 用来记录当前窗口中满足”目标字符串字符出现的次数”的个数(有点绕。。书面表达是个硬伤T T)

二、确定左边窗口何时向右收敛

  • 答案就是在右边窗口挪动时找到一对符合要求的子串时,就需要通过移动左边窗口,来尝试获取长度更小的符合要求的子串。

三、各个变量在何时被维护?仔细想想,有几个关键时刻:

1、右边窗口往右滑动:

  • 如果碰到了目标字符串中的字符,我们需要维护什么变量?主要就是要维护当前窗口目标字符hash桶中的计数器;
  • 如果碰到了目标字符串中的字符,而且在维护完hash桶计数器后,发现与目标字符要求出现次数相同,那么我们就认为有一个目标字符满足了出现次数的要求,让validCount加1。

2、每次窗口中字符满足覆盖要求时:

  • 试算一下当前窗口的长度是否比以往更小,是则需要更新最小子串的开始索引以及其长度;
  • 同时,虽然本次窗口中的字符已经满足覆盖要求,但如果我们把左边窗口往右边收敛,是不是有可能获取到一个更短但也满足覆盖要求的子串?所以我们就尝试左边窗口向右收敛;
  • 在向右收敛的过程中,如果(条件1 )本次剔除的最左边的字符刚好是目标字符串中的字符,我们就再判断下是不是(条件2)失去这个字符后不再满足覆盖要求,如果条件1与2都符合的话就要让validCount减1,而不管符不符合条件2,只要满足条件1我们都要让当前窗口字符对应的计数器减1。

最终返回目标子串即可。

某一时刻的窗口快照.png

代码实现如下:

public class Solution {

    public String minWindow(String s, String t) {
        // 用以记录需要的字符以及每个字符应该出现次数
        Map<Character, Integer> needs = new HashMap<>();
        for(int i = 0; i < t.length(); i++) {
            char c = t.charAt(i);
            needs.put(c, needs.getOrDefault(c, 0) + 1);
        }
        // 用以记录当前窗口中符合needs的字符以及每个字符出现的次数
        Map<Character, Integer> windows = new HashMap<>();
        // 滑动窗口的左右窗口,左闭右开[left,right)
        int left = 0, right = 0;
        // 用来记录结果:子串的开始索引以及长度
        int start = 0, len = Integer.MAX_VALUE;
        // 记录当前窗口中字符出现次数满足needs要求的个数,只有当全部字符出现个数都满足了,左窗口才尝试向右收缩
        int validCount = 0;
        while (right < s.length()) {
            // 往右滑动
            char c = s.charAt(right++);
            // 找到一个需要的字符
            if (needs.containsKey(c)) {
                // 当前窗口统计该字符的hash桶计数器+1
                windows.put(c, windows.getOrDefault(c, 0) + 1);
                // 出现的次数刚好达到要求
                if (windows.get(c).equals(needs.get(c))) {
                    // 表示当前窗口中,当前字符的出现次数已达到要求
                    validCount++;
                }
            }

            // 所有字符的出现字数都满足了,那么准备左边窗口向前收缩(尝试圈一个更小的子串)
            while (validCount == needs.size()) {
                // 每次达到字符数要求,就试图更新一下字串开始索引与长度
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                // 左边窗口向前收缩
                char l = s.charAt(left++);
                // 如果收缩前最左边的字符为需要的字符,那么更新当前窗口完成的字符个数,并更新窗口中有效字符个数
                if (needs.containsKey(l)) {
                    // 右移去掉该字符后,出现次数达不到要求,那么符合needs要求的个数要减1
                    if (windows.get(l).equals(needs.get(l))) {
                        validCount--;
                    }
                    windows.put(l, windows.get(l) - 1);
                }
            }
        }
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }

}

相关文章

网友评论

      本文标题:以leetcode 76. 最小覆盖子串为例,学习滑动窗口的技巧

      本文链接:https://www.haomeiwen.com/subject/mgetjrtx.html