Java题解LeetCode——76.最小覆盖子串

Java题解LeetCode——76.最小覆盖子串

题目

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

示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

解析

今天仔细看了题解,原来子串是连续的,以前我以为是不连续的。
注意,在官方题解中说字符串T中可能出现重复字符,所以在包含字符的同时次数也要相等。
在这里插入图片描述
用滑动窗口去做,可以理解为双指针,指针中间就是窗口,两个指针的起始位置都在字符串的最左边。先让右指针右移,判断满足题目条件后,让左指针右移,目的是看看能不能缩小窗口(减小子串长度),distance 的作用是记录窗口内 t 串字符出现的频数,初始化为0,当右指针右移时,一旦发现指针对应的字符和 t 串中的字符匹配上了distance++, 当频数满足题意时,就是distance == t 串的长度,这个窗口(子串)就确定了,然后让左指针右移,看看窗口能不能缩小,左指针的指针移动逻辑和右指针相同,就是一旦发现左指针对应的字符串匹配上了,distance --,这样 distance != t 串的长度,不满足条件那么就不能让左指针右移了。

代码

//滑动窗口,可以理解为双指针,一左一右,中间就是窗口
class Solution {
    public String minWindow(String s, String t) {
        int sLen = s.length();
        int tLen = t.length();
        if (sLen == 0 || tLen == 0 || sLen < tLen){
            return "";
        }

        char[] sCharArray = s.toCharArray();
        char[] tCharArray = t.toCharArray();

        // ascii('z') = 122,存字母对应的ASCII码
        int[] winFreq = new int[128]; //用来记录窗口内(也就是当前子串)的字符情况
        int[] tFreq = new int[128]; //用来记录t字符的频数
        for (char c : tCharArray){
            tFreq[c]++; //桶排序中统计出现次数的思想
        }

        //滑动窗口内部包含多少T中的字符,对应字符频数超过不重复计算
        int distance = 0; //用来记录t中字符在窗口中出现的频数
        int minLen = sLen + 1; //最小子串的长度,用于最后的substring
        int begin = 0; //开始下标,用于最后的substring

        //左右两个指针,也就是窗口的两个边界
        int left = 0;
        int right = 0;

        //左闭右开区间[left,right)
        while (right < sLen){ //当右指针没移动到头时
            //右指针向右移动逻辑

			//这一步可以省略,建议加上,更符合逻辑
            if (tFreq[sCharArray[right]] == 0){ //右指针的字符不是t中字符时
                right++; //向右移动
                continue;
            }

            if (winFreq[sCharArray[right]]<tFreq[sCharArray[right]]){
                distance++; //t中字符在窗口中出现的频数+1
            }
            winFreq[sCharArray[right]]++; //窗口内(也就是当前子串)当前右指针对应的字符频次+1
            right++; //指针右移

            //执行该循环就说明,窗口的右边界已经找到了一个符合条件的窗口,但是左边界没有动,接下来尝试右移左边界缩小窗口,看看能不让子串更小
            while(distance == tLen){ //当t中字符在窗口中出现的频数满足题意时

                //更新最小子串长度
                if (right - left < minLen){
                    minLen = right - left;
                    begin = left;
                }

                //左指针向右移动的逻辑
                
				//这一个步可以省略,建议加上,更符合逻辑
                if (tFreq[sCharArray[left]] == 0){ //左指针的字符不是t中字符时
                    left++; //向右移动
                    continue;
                }

                //当前左指针对应的字符频次和t串对应的字符频次相等
                if (winFreq[sCharArray[left]] == tFreq[sCharArray[left]]){
                    distance--; //t中字符在窗口中出现的频数-1(因为左指针要右移,所以频次会减少)
                }
                winFreq[sCharArray[left]]--; //窗口内(也就是当前子串)当前左指针对应的字符频次-1
                left++; //左指针右移
            }
        }

        if (minLen == sLen+1){ //最小长度没有变化,说明无法匹配,直接返回空串
            return "";
        }
        return s.substring(begin, begin+minLen);

    }
}

在这里插入图片描述

代码小优化

就做了一个小调整,让时间少了1ms

把上面的代码中
sCharArray[right] 这个值提取出来
char sCharRight = sCharArray[right]; //拿到当前右指针对应的字符

同理左指针的也一样

调整后代码整体如下

//滑动窗口,可以理解为双指针,一左一右,中间就是窗口
class Solution {
    public String minWindow(String s, String t) {
        int sLen = s.length();
        int tLen = t.length();
        if (sLen == 0 || tLen == 0 || sLen < tLen){
            return "";
        }

        char[] sCharArray = s.toCharArray();
        char[] tCharArray = t.toCharArray();

        // ascii('z') = 122,存字母对应的ASCII码
        int[] winFreq = new int[128]; //用来记录窗口内(也就是当前子串)的字符情况
        int[] tFreq = new int[128]; //用来记录t字符的频数
        for (char c : tCharArray){
            tFreq[c]++; //桶排序中统计出现次数的思想
        }

        //滑动窗口内部包含多少T中的字符,对应字符频数超过不重复计算
        int distance = 0; //用来记录t中字符在窗口中出现的频数
        int minLen = sLen + 1; //最小子串的长度,用于最后的substring
        int begin = 0; //开始下标,用于最后的substring

        //左右两个指针,也就是窗口的两个边界
        int left = 0;
        int right = 0;

        //左闭右开区间[left,right)
        while (right < sLen){ //当右指针没移动到头时
            //右指针向右移动逻辑
            char sCharRight = sCharArray[right]; //拿到当前右指针对应的字符
            if (tFreq[sCharRight] == 0){ //右指针的字符不是t中字符时
                right++; //向右移动
                continue;
            }

            if (winFreq[sCharRight]<tFreq[sCharRight]){
                distance++; //t中字符在窗口中出现的频数+1
            }
            winFreq[sCharRight]++; //窗口内(也就是当前子串)当前右指针对应的字符频次+1
            right++; //指针右移

            //执行该循环就说明,窗口的右边界已经找到了一个符合条件的窗口,但是左边界没有动,接下来尝试右移左边界缩小窗口,看看能不让子串更小
            while(distance == tLen){ //当t中字符在窗口中出现的频数满足题意时

                //更新最小子串长度
                if (right - left < minLen){
                    minLen = right - left;
                    begin = left;
                }

                //左指针向右移动的逻辑
                char sCharLeft = sCharArray[left]; //拿到当前左指针对应的字符
                if (tFreq[sCharLeft] == 0){ //左指针的字符不是t中字符时
                    left++; //向右移动
                    continue;
                }

                //当前左指针对应的字符频次和t串对应的字符频次相等
                if (winFreq[sCharLeft] == tFreq[sCharLeft]){
                    distance--; //t中字符在窗口中出现的频数-1(因为左指针要右移,所以频次会减少)
                }
                winFreq[sCharLeft]--; //窗口内(也就是当前子串)当前左指针对应的字符频次-1
                left++; //左指针右移
            }
        }

        if (minLen == sLen+1){ //最小长度没有变化,说明无法匹配,直接返回空串
            return "";
        }
        return s.substring(begin, begin+minLen);

    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

影龙武

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

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

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

打赏作者

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

抵扣说明:

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

余额充值