与魔鬼做契约!
题目描述
给你一个字符串 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);
}
};
代码分析
代码逻辑
-
初始化:
-
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
中的所有字符。
-
-
滑动窗口遍历:
-
for (int l = 0, r = 0; r < str.size(); r++)
:使用双指针l
和r
来遍历字符串str
,r
是右指针,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
,则更新len
和start
。
-
-
-
-
返回结果:
-
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"
,代码的执行过程如下:
-
初始化:
-
cnts = [..., -1, -1, -1, ...]
(对应字符A
,B
,C
欠债)。 -
len = INT_MAX
,start = 0
,debt = 3
。
-
-
遍历过程:
-
r = 0
,str[r] = 'A'
,cnts = [..., 0, -1, -1, ...]
,debt = 2
。 -
r = 1
,str[r] = 'D'
,cnts = [..., 0, -1, -1, ...]
,debt = 2
。 -
r = 2
,str[r] = 'O'
,cnts = [..., 0, -1, -1, ...]
,debt = 2
。 -
r = 3
,str[r] = 'B'
,cnts = [..., 0, 0, -1, ...]
,debt = 1
。 -
r = 4
,str[r] = 'E'
,cnts = [..., 0, 0, -1, ...]
,debt = 1
。 r = 5
,str[r] = 'C'
,cnts = [..., 0, 0, 0, ...]
,debt = 0
。-
进入
if (debt == 0)
,l = 0
,cnts = [..., 0, 0, 0, ...]
。 -
更新
len = 6
,start = 0
。
-
r = 6
,str[r] = 'O'
,cnts = [..., 0, 0, 0, ...]
,debt = 0
。-
进入
if (debt == 0)
,l = 0
,cnts = [..., 0, 0, 1, ...]
。 -
l = 1
,cnts = [..., 0, 1, 0, ...]
。 -
更新
len = 6
,start = 0
。
-
r = 7
,str[r] = 'D'
,cnts = [..., 0, 1, 0, ...]
,debt = 0
。-
进入
if (debt == 0)
,l = 1
,cnts = [..., 0, 1, 0, ...]
。 -
l = 2
,cnts = [..., 0, 0, 0, ...]
。 -
更新
len = 6
,start = 0
。
-
r = 8
,str[r] = 'E'
,cnts = [..., 0, 0, 0, ...]
,debt = 0
。-
进入
if (debt == 0)
,l = 2
,cnts = [..., 0, 0, 0, ...]
。 -
l = 3
,cnts = [..., 0, 1, 0, ...]
。 -
更新
len = 5
,start = 3
。
-
r = 9
,str[r] = 'B'
,cnts = [..., 0, 1, 0, ...]
,debt = 0
。-
进入
if (debt == 0)
,l = 3
,cnts = [..., 0, 1, 0, ...]
。 -
l = 4
,cnts = [..., 0, 0, 0, ...]
。 -
更新
len = 5
,start = 3
。
-
r = 10
,str[r] = 'A'
,cnts = [..., 0, 0, 0, ...]
,debt = 0
。-
进入
if (debt == 0)
,l = 4
,cnts = [..., 0, 0, 0, ...]
。 -
l = 5
,cnts = [..., 0, 0, 1, ...]
。 -
更新
len = 4
,start = 5
。
-
-
最终结果为 len = 4
,start = 5
,最小覆盖子串为 "BANC"
。