Leetcode 76-最小覆盖子串

本文详细解析了如何使用滑动窗口算法解决LeetCode中的字符串子串查找问题。通过维护两个指针,动态调整窗口大小,找到包含目标字符串所有字符的最小子串。在解题过程中,重点讨论了何时扩展和收缩窗口,以及如何更新计数器的条件。此方法适用于处理字符串匹配和查找效率高的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
在这里插入图片描述

题解一

参考灵茶山艾府法一

题解二

图片转载自LeetCode-Soution
思路和算法

本问题要求我们返回字符串 s 中包含字符串 t 的全部字符的最小窗口。我们称包含 t 的全部字母的窗口为「可行」窗口。

我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 s 上滑动窗口,通过移动 r 指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。
在这里插入图片描述

解题过程

一、 初始化两个字符数组have[]和need[]分别记录s和t中每个字符的出现次数,用变量pass记录t中字符的总个数,用passed记录s的子串中包含t中全部某个种类全部字符的个数
二、使用滑动窗口在s中遍历,通过两个指针left和right来维护窗口的左右边界,right循环遍历s字符串:

  • 如果窗口此时此时have[right]=need[right],说明s中新覆盖了t中一个字符,passed++
  • 当窗口内的字符种类数等于t中的字符种类数时(passed==pass),尝试循环移动左指针缩小窗口,同时更新最小窗口的边界。 即min=right-left+1,更新完最小窗口大小后 left++,have[l]–
  • 更新完最小窗口大小后 right右移,窗口扩大

三、返回最小窗口对应的子字符串或空字符串(如果没有找到符合条件的窗口)。

复杂度
时间复杂度:O(n),其中n是字符串s的长度。每个字符最多被访问两次,一次作为窗口的右边界,一次作为窗口的左边界。
空间复杂度:O(1),使用了固定大小的数组来记录字符的出现次数,不随输入规模的变化而变化(这里假设字符集大小固定为128)。

class Solution {
    public String minWindow(String s, String t) {
        if (s == null || s == "" || t == null || t == "" || s.length() < t.length())  return "";
        //维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
        //ASCII表总长128
        int[] need = new int[128];
        int[] have = new int[128];

        //将目标字符串指定字符的出现次数记录
        for (int i = 0; i < t.length(); i++) {
            need[t.charAt(i)]++;
        } 
        int pass=0;
        for (int i = 0; i < need.length; i++) {
            if(need[i]>0) pass++;
        } 
        //分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
        //已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
        int left = 0, right = 0, min = s.length() + 1, passed = 0, start = 0;
        while(right<s.length()){
            char r = s.charAt(right);
            have[r]++;
            //注意这里是=而不是>=,避免重复计数
            if(have[r]==need[r]) passed++;

            //当子串中通过的字符数==t中包含的字符数
            while(pass==passed){
                char l=s.charAt(left);
                //如果have[l]刚好等于need[l],那么左移left会使得pass的数减少,会退出左移的循环
                //更新最小子串的长度和起始位置
                if(min>right-left+1){
                    min=right-left+1;
                    start=left;
                }
                //右移左指针
                if(have[l]==need[l]) passed--;
                left++;
                have[l]--;
               
            }
            //右指针右移
            right++;
        
        }
         //如果最小长度还为初始值,说明没有符合条件的子串
        if (min == s.length() + 1) {
            return "";
        }
        //返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
        return s.substring(start, start + min); 
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值