算法-滑动窗口-最小覆盖子串

算法-滑动窗口-最小覆盖子串

1 概述

1.1 题目出处

https://leetcode-cn.com/problems/minimum-window-substring/

1.2 题目描述

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

示例:

输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
说明:

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

2 可扩大和缩小的滑动窗口

2.1 解题思路

这个题目关键是怎么判断某个连续字符串不按顺序的包含给定的字符串,依次比较的话是很麻烦的。

但我们可以换一种思路,统计当前窗口内的有效字符数量。这里的有效字符是指某个字符属于给定的目标字符串之一,且该字符当前累积数量小于给定字符数量时,可统计为有效字符count

按照这个思路,固定左边界left,一直往右遍历字符,直到统计到的有效字符数达到目标字符数,则肯定包含了目标字符串。

此时,需要左边界left++来右移,去掉无效的字符。这里无效的字符包括两类:

  • 不属于目标字符串内的字符
  • 数量超出目标字符要求的字符

左边界右移的结束条件为某个有效字符的累积数量首次小于目标字符数量。

左边界右移结束后,将右边界right确定为当前趟遍历的下标i,此时左右边界框定窗口就是包含了目标字符串的覆盖子串!随后计算窗口长度是否小于此前的最小窗口长度min,如果小于就替换min,并将当前窗口内的字符串替换minResult

最后将有效字符数count减一表示已经不满足,并将窗口左边界left右移1位,并开始下一趟遍历。

2.2 代码

class Solution {
    public String minWindow(String s, String t) {
        if(s == null || t == null || s.length() < t.length()){
            return "";
        }

        int[] targetCharCnt = new int[128];
        int[] charCnt = new int[128];

        char[] cs = t.toCharArray();
        
        for(char c : cs){
            targetCharCnt[c] += 1;
        }

        // 当前有效字符数
        int count = 0;

        cs = s.toCharArray();
        int left = 0;
        int right = 0;
        int min = 0;
        String minResult = "";
        for(int i = 0; i < cs.length; i++){
            char c = cs[i];
            if(targetCharCnt[c] > 0){
                if(targetCharCnt[c] > charCnt[c]){
                    // 此时该字符数量还未达到目标数量,需要累计
                    count++;
                }
                // 累加窗口内的该字符数量
                charCnt[c]++;
            }
            if(count == t.length()){
                // 说明找到了包含目标字符串的字符串
                // 此时我们需要缩小左边界,去掉无效字符
                while(count == t.length()){
                    char leftC = cs[left];
                    if(charCnt[leftC] > 0){
                        if(--charCnt[leftC] < targetCharCnt[leftC]){
                            //说明是靠右的有效字符
                            break;
                        }else{
                            // 说明是靠左的超过目标重复次数的字符,忽略该字符,继续右移左边界
                            // 比如需要两个B,即BB,但在这个字符串中有三个B,如ABDDBCB,则忽略最左那个B
                            left++;
                        }
                    }else{
                        // 非目标字符,右移左边界
                        left++;
                    }
                }
                // 更新右边界为当前字符下标
                right = i;
                if(min == 0){
                    // 初始时
                    min = right - left + 1;
                    minResult = s.substring(left, right + 1);
                }else if(min > right - left + 1){
                    // 之前有符合条件字符串,且当前找到的字符串长度小于之前的,就替换
                    min = right - left + 1;
                    minResult = s.substring(left, right + 1);
                }
                // 有效字符减一,窗口左边界右移一位
                count--;
                left++;
            }
        }
        return minResult;
    }
}

2.3 时间复杂度

在这里插入图片描述
O(N+T)

  • N表示原字符串,T表示目标字符串。最坏情况遍历原字符串两次

2.4 空间复杂度

  • C为128,记录每个字符出现次数

你可能感兴趣的:(算法)