力扣刷题--76.最小覆盖子串

与魔鬼做契约!

题目描述

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

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"

输出:"BANC"

解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"

输出:"a"

解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"

输出: ""

解释: t 中两个字符 'a' 均应包含在 s 的子串中,

因此没有符合条件的子字符串,返回空字符串。

提示:

m == s.length

n == t.length

1 <= m, n <= 105

s 和 t 由英文字母组成

思路分析

  • 核心:滑动窗口

  • 设置一张欠债表,记录下target当中的字符数和种类

  • 右边界r,往右扩是为了还债,比如r=1时,b的债务-2+1为-1,一直到还清欠债表,即a,b,c的欠债值为0,期间碰到其它字母如t,无所谓,加入到欠债表当中,不影响

  • 左边界l,往右逐渐缩小,直至满足最短的覆盖字串。

完整代码

// 滑动窗口
// 对t进行统计:
// 债务表:
// a:-1
// b:-2
// c:-2 总共5个, 代表t
// 遍历s,每到一个数,还一个款
// 比如到A,还给A+1.如果不是t当中的一个,就不是有效还款
// 在满足条件的情况下,尽可能缩短
#include <algorithm>
#include <climits>
#include <string>
#include <vector>

//单调性:范围越大,越容易满足条件
//左边越缩,越不容易满足条件
class Solution {
public:
    string minWindow(string str,string tar) {
        if (str.length() < tar.length()) {
            return "";
        }
        //cnts欠债表
        vector<int> cnts(256, 0); // 初始化一个大小为256的数组
        for (auto cha : tar) {
            cnts[cha]--; // 记录目标字符串中每个字符的欠债情况
        }
        int len = INT_MAX;       // 最小覆盖子串的长度
        int start = 0;           // 最小覆盖子串的起始位置
        int debt = tar.length(); // 初始债务为目标字符串的长度
        for (int l = 0, r = 0; r < str.size(); r++) {
            // s[r]当前字符 ->int
            // cnts[s[r]]:当前字符欠债情况,负数就是欠债,正数就是多给的
            if (cnts[str[r]]++ <0) { // 当前字符欠债情况,负数就是欠债,正数就是多给的
                debt--;
            }
            if (debt == 0) { // 当债务为0时,说明找到了一个覆盖子串
               //r位置结尾,真的有覆盖字串!
                while (cnts[str[l]] > 0) { // 左指针右移,直到不能再移
                    //l位置的字符词频--,能拿回
                    cnts[str[l++]]--;
                }
                //从while里面出来,r位置结尾的最小覆盖字串
                //l....r  
                if (r - l + 1 < len) { // 更新最小覆盖子串的长度和起始位置
                    len = r - l + 1;
                    start = l;
                }
            }
        }
        //如果一直没有答案,返回空字符串
        return len == INT_MAX ? "" : str.substr(start, len);
    }
};

代码分析

代码逻辑

  1. 初始化

    • vector<int> cnts(256, 0):初始化一个大小为256的数组 cnts,用于记录每个字符的欠债情况。

    • for (auto cha : tar):遍历目标字符串 tar,将每个字符的欠债情况记录在 cnts 中,即 cnts[cha]--,表示每个字符都欠一次。

    • int len = INT_MAX:初始化最小覆盖子串的长度为 INT_MAX

    • int start = 0:初始化最小覆盖子串的起始位置为 0

    • int debt = tar.length():初始化债务为 tar 的长度,表示需要找到 tar 中的所有字符。

  2. 滑动窗口遍历

    • for (int l = 0, r = 0; r < str.size(); r++):使用双指针 lr 来遍历字符串 strr 是右指针,l 是左指针。

    • **处理当前字符 str[r]**:
      • if (cnts[str[r]]++ < 0):如果当前字符 str[r] 欠债(cnts[str[r]] < 0),则将 cnts[str[r]] 增加,并减少债务 debt--

    • 检查是否找到覆盖子串
      • if (debt == 0):如果债务为 0,说明当前窗口 [l, r] 包含了目标字符串 tar 的所有字符。

      • 左指针右移
        • while (cnts[str[l]] > 0):当 str[l] 的字符词频大于 0 时,说明当前字符 str[l] 是多余的,可以右移左指针 l,同时将 cnts[str[l]] 减少。

      • 更新最小覆盖子串
        • if (r - l + 1 < len):如果当前窗口 [l, r] 的长度小于之前记录的最小覆盖子串长度 len,则更新 lenstart

  3. 返回结果

    • return len == INT_MAX ? "" : str.substr(start, len):如果 len 仍然是 INT_MAX,说明没有找到覆盖子串,返回空字符串;否则返回从 start 开始长度为 len 的子串。

时间复杂度分析

  • 时间复杂度:O(n),其中 n 是字符串 str 的长度。每个字符最多被遍历两次(一次由右指针 r,一次由左指针 l)。

  • 空间复杂度:O(1),cnts 的大小固定为256,与输入规模无关。

示例

假设输入字符串 str = "ADOBECODEBANC",目标字符串 tar = "ABC",代码的执行过程如下:

  1. 初始化

    • cnts = [..., -1, -1, -1, ...](对应字符 A, B, C 欠债)。

    • len = INT_MAXstart = 0debt = 3

  2. 遍历过程

    • r = 0str[r] = 'A'cnts = [..., 0, -1, -1, ...]debt = 2

    • r = 1str[r] = 'D'cnts = [..., 0, -1, -1, ...]debt = 2

    • r = 2str[r] = 'O'cnts = [..., 0, -1, -1, ...]debt = 2

    • r = 3str[r] = 'B'cnts = [..., 0, 0, -1, ...]debt = 1

    • r = 4str[r] = 'E'cnts = [..., 0, 0, -1, ...]debt = 1

    • r = 5str[r] = 'C'cnts = [..., 0, 0, 0, ...]debt = 0
      • 进入 if (debt == 0)l = 0cnts = [..., 0, 0, 0, ...]

      • 更新 len = 6start = 0

    • r = 6str[r] = 'O'cnts = [..., 0, 0, 0, ...]debt = 0
      • 进入 if (debt == 0)l = 0cnts = [..., 0, 0, 1, ...]

      • l = 1cnts = [..., 0, 1, 0, ...]

      • 更新 len = 6start = 0

    • r = 7str[r] = 'D'cnts = [..., 0, 1, 0, ...]debt = 0
      • 进入 if (debt == 0)l = 1cnts = [..., 0, 1, 0, ...]

      • l = 2cnts = [..., 0, 0, 0, ...]

      • 更新 len = 6start = 0

    • r = 8str[r] = 'E'cnts = [..., 0, 0, 0, ...]debt = 0
      • 进入 if (debt == 0)l = 2cnts = [..., 0, 0, 0, ...]

      • l = 3cnts = [..., 0, 1, 0, ...]

      • 更新 len = 5start = 3

    • r = 9str[r] = 'B'cnts = [..., 0, 1, 0, ...]debt = 0
      • 进入 if (debt == 0)l = 3cnts = [..., 0, 1, 0, ...]

      • l = 4cnts = [..., 0, 0, 0, ...]

      • 更新 len = 5start = 3

    • r = 10str[r] = 'A'cnts = [..., 0, 0, 0, ...]debt = 0
      • 进入 if (debt == 0)l = 4cnts = [..., 0, 0, 0, ...]

      • l = 5cnts = [..., 0, 0, 1, ...]

      • 更新 len = 4start = 5

最终结果为 len = 4start = 5,最小覆盖子串为 "BANC"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码农小小苏

感谢大佬支持!!!

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

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

打赏作者

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

抵扣说明:

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

余额充值