最小覆盖子串

继续每日一题,今天给大家继续分享一道滑动窗口的题目

题目稍微难度稍微有一点,但是我会带着大家慢慢分析

题目描述:

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

题目示例:
在这里插入图片描述

首先先明确一个点:什么叫子串?

子串的含义是:有A B两个字符串,如果A中对于B的每个字符出现的频次和B相等,就可判定B是A的子串

如:A=“ABCDAA” B=“DBC” B是A的子串

接下来我们再看一下滑动窗口的常用套路:

  • 定义left,right指针用于维护窗口大小
  • 移动右指针扩大窗口范围来匹配我们需要的信息
  • 匹配到信息后,执行响应的逻辑,移动左指针缩小窗口,以便让窗口向右继续滑动

注意:所有类型的滑动窗口类型的题目均可以按照这个套路去做

什么时候算是匹配成功,本题的匹配条件是找到子串,即找到窗口内对应的字符串,使的t是窗口的子串,结合上面给大家对子串的定义,也就是转化为找字符出现的频次

此处有一个技巧,针对所有子串相关的频次统计问题,均可以利用数组来对比,但本题稍微有些特殊的点在于两个字符串的字符没有限制大小写,所以我们可以定义一个size=128的数组(ASCII 128 足够覆盖常见字符)来统计每个字符出现的频次

int[] need = new int[128];
int[] window = new int[128];

其中:

  • need是需要我们匹配上的,即题目中给的t字符串中每个字符出现的次数
  • window是窗口内每个字符出现的次数

什么时候才能算匹配上呢?文字说明不好理解,我列举一个case:

s=“AABCCD” t=“AABCD”

need=[0,0,…,2,1,1,1,0,0…,0]
当index=1时,A的数量=2,和need里A的数量相等,则认为匹配上了1个字符

所以我们在匹配之前,还需要找到需要匹配几个字符,这个也很简单,我们只需要遍历need,找到有多少不为0的位置(类似布隆过滤器)便找到了几个需要匹配的字符

以题目中的case为例:

s = “ADOBECODEBANC”, t = “ABC”

need=[0,0,…,1,1,1,0,0…,0]

有三个不为0的位置,所以待匹配的字符数量totalChar=3

当匹配上的次数matchs==totalChar时,我们需要计算是否是最小子串,并移动left,在移动left的过程中,要保证窗口仍然包含t的所有字符,直到不满足时再继续移动right。

里面有几个核心的代码:

  • 声明匹配的数组
        int[] need = new int[128];
        int[] window = new int[128];
        for (Character c : t.toCharArray()) {
            need[c]++;
        }
  • 查找需要匹配的字符数量
        int totalChars = 0;
        for (int n : need) {
            if (n > 0) {
                totalChars++;
            }
        }
  • 移动右指针,并计算匹配上的次数
            if (right < s.length()) {
                Character rightC = s.charAt(right);
                if (need[rightC] > 0) {
                    window[rightC]++;
                    if (window[rightC] == need[rightC]) {
                        matchs++;
                    }
                }

            }
            right++;
  • 匹配成功,执行最小子串查找逻辑
            while (matchs == totalChars) {
                Character leftC = s.charAt(left);
                if (right - left < minLength) {
                    minLength = right - left;
                    ans = s.substring(left, right);
                }
                if (need[leftC] > 0) {
                    if (window[leftC] == need[leftC]) {
                        matchs--;
                    }
                    window[leftC]--;
                }
                left++;
            }

当然很多同学可能对上面这段代码里的

                if (need[leftC] > 0) {
                    if (window[leftC] == need[leftC]) {
                        matchs--;
                    }

                    window[leftC]--;
                }

有些许不理解,我同样用上面的case帮大家理解一下:

s = “ADOBECODEBANC”, t = “ABC”

need=[0,0,…,1,1,1,0,0…,0]

在遍历的过程中,B可能出现多次,window=[0,0,…,1,2,1,0,0…,0]

移动指针后,虽然window[c]–:window=[0,0,…,1,1,1,0,0…,0]

但是仍然包含B这个字符,所以matchs不能减一,只有在当前从窗口内,该字符出现的数量恰好和待匹配字符数量相等的时候,一旦移动指针,就少了该字符的匹配,所以才能matchs减一

整体如下:

    public static String minWindow(String s, String t) {
        int[] need = new int[128];
        int[] window = new int[128];
        for (Character c : t.toCharArray()) {
            need[c]++;
        }
        int left = 0, right = 0;
        int matchs = 0;
        int minLength = Integer.MAX_VALUE;
        String ans = "";
        int totalChars = 0;
        for (int n : need) {
            if (n > 0) {
                totalChars++;
            }
        }
        while (right <= s.length()) {
            while (matchs == totalChars) {
                Character leftC = s.charAt(left);
                if (right - left < minLength) {
                    minLength = right - left;
                    ans = s.substring(left, right);
                }
                if (need[leftC] > 0) {
                    if (window[leftC] == need[leftC]) {
                        matchs--;
                    }

                    window[leftC]--;
                }
                left++;
            }
            if (right < s.length()) {
                Character rightC = s.charAt(right);
                if (need[rightC] > 0) {
                    window[rightC]++;
                    if (window[rightC] == need[rightC]) {
                        matchs++;
                    }
                }

            }
            right++;

        }
        return ans;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZNineSun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值