Loading

Hard | LeetCode 76. 最小覆盖子串 | 滑动窗口

76. 最小覆盖子串

给你一个字符串 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
  • st 由英文字母组成

进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?

解体思路

方法一: 滑动窗口

思路很容易想到。首先设置两个指针left, right, 分别代表滑动窗口的左右边界, 初始时指向为0。首先先让右边界逐渐向右移动, 直到滑动窗口内部已经包含了T中的所有字符。

在滑动窗口内部已经包含了T中所有字符过后, 让左边界逐渐向右移动, 尽可能得缩小滑动滑动大小。最小滑动窗口的大小总是在移动左边界的过程中得出。

在恰好不能包含T中所有字符时, 可以对最小长度进行更新。并且记录下此时窗口的起始位置和长度。

在判断滑动窗口内字符是否包含了T中所有字符时, 有一个小小的技巧。

设置一个变量distance表示窗口内字符里T中的字符出现的个数。对T中某个字符, 窗口内字符出现的个数最大为T中此字符个数。

比如窗口字符为aaabbbcccc, 而T字符为aabbc, 那么对于窗口的字符统计为winFreq['a'] = 3, winFreq['b'] = 3, winFreq['c'] = 3, 距离distance = 3

这样, distance最大为T字符串的长度。当distance == tLen时, 代表此时滑动窗口已经包含了所有字符(但是winFreq内的字符统计, 可能会比T串字符出现的个数多, 这是允许的)。

同理在左边界逐渐向右移动, 恰好不包含T中字符时, 也是同样的方法, 当某个字符窗口个数和T串个数恰好相等时, 如果左边界继续右移, 则窗口将不再包含T中所有字符。此时需要将distance--。

public String minWindow(String s, String t) {
    int sLen = s.length(), tLen = t.length();
    char[] ss = s.toCharArray();
    char[] tt = t.toCharArray();
    // winFreq, tFreq分别用于统计滑动窗口内部和t串内字符出现次数
    int[] tFreq = new int[128];
    int[] winFreq = new int[128];
    for (char c : tt) {
        tFreq[c]++;
    }
    int left = 0, right = 0;
    // distance表示在s字符串中,t中字符出现的个数
    // 对单个字符而言, 最多出现t中字符串出现的次数
    int distance = 0;
    // 最小子串的长度
    int minLen = Integer.MAX_VALUE;
    int resBegin = 0;
    while (right < sLen) {
        if (tFreq[ss[right]] > 0) {
            // 如果右边界字符是t中出现的字符
            if (winFreq[ss[right]] < tFreq[ss[right]]) {
                // 计数距离自增
                distance++;
            }
            // 滑动窗口内对此字符计数
            winFreq[ss[right]]++;

            while (distance == tLen) {
                // distance == tLen 表示滑动窗口当中已经包含了全部T中的字符
                // 此时需要左窗口不断向右滑动
                if (tFreq[ss[left]] > 0) {
                    // 左边界的字符在滑动窗口出现,
                    if (winFreq[ss[left]] == tFreq[ss[left]]) {
                        if (minLen > right - left + 1) {
                            // 需要更新长度
                            minLen = right - left + 1;
                            resBegin = left;
                        }
                        distance--;
                    }
                    // 修改次数
                    winFreq[ss[left]]--;
                }
                left++;
            }

        }
        right++;
    }
    if (minLen == Integer.MAX_VALUE) {
        // S中没有包含T的最小覆盖子串
        return "";
    }

    return s.substring(resBegin, resBegin + minLen);
}
posted @ 2021-05-09 15:06  反身而诚、  阅读(45)  评论(0编辑  收藏  举报