文章目录
- 76. 最小覆盖子串:
-
- 样例 1:
- 样例 2:
- 样例 3:
- 提示:
- 进阶:
- 分析:
-
- 在这里插入图片描述
- 题解:
-
- rust:
- go:
- c++:
- python:
- java:
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 5
-
s
和t
由英文字母组成
进阶:
你能设计一个在 o(m + n)
时间内解决此问题的算法吗?
分析:
- 面对这道算法题目,二当家的再次陷入了沉思。
- 直接采用双层循环暴力解决的话,时间复杂度会比较高,恐怕过不了用例,没有去试。
- 题目并不要求字符的顺序,只要求了每种字符的数量,那首先就想到要做计数。
- 接下来考虑如何降低时间复杂度,每次循环都从新匹配子串是低效的,要尽可能复用,滑动窗口在这里非常合适,使用双指针,先直接移动右指针找到第一个满足条件的子串,记下长度作为备选结果,接下来移动左指针,当不满足覆盖子串的条件时就继续移动右置针直到满足覆盖子串的条件,并比较子串的长度,取较小的子串长度作为备选结果,重复该操作,直到循环遍历完毕。
- 如何高效判断遍历的子串已经满足覆盖子串呢?字符计数这时候就要大展拳脚了,先在遍历字符串
s
之前,先对字符串t
进行一次遍历,做初始化计数,记录下每种字符的个数(题解中这里使用了减法,使用加法也是可以的,只是后面的加减法就要变),在遍历s
时,移动右指针就是延长了覆盖子串,同时修改计数,这里的加减法计算要和前面初始化的相反,判断计数是否为0,如果变为0则表示,这种字符的个数已经没有差异了,但是我们需要覆盖了t
中的每一种字符,所以需要判断26
个字符的个数是不是都够了,如果都够了,就是满足了覆盖子串,接下来就移动左指针,同时修改计数,这里的加减计算要和右指针移动的相反。 - 当某个字符的计数变为0时,我们需要判断
26
种字符的字符个数是不是都满足了,这里就需要26次循环,是否有更高效的办法呢?我们可以额外增加一个变量记录有差异的字符种类数(记录有差异的字符数也是可以的,但是后面的逻辑会有一点区别,思想大致相同), 初始化时顺便初始化该变量,在遍历匹配中,每当有字符计数变为0,就修改这个变量,如果这个变量变为0则表示完全覆盖,从而提高效率。 - 只看文字可能不便理解,建议对照着熟悉的语言的题解一起看,希望可以有助学习理解。
题解:
rust:
impl Solution {
pub fn min_window(s: String, t: String) -> String {
// 少于目标字符串中数量的字符数量
let mut diff_char_count = 0;
// 字符计数器
let mut cnt = vec![0; 128];
// 初始化
t.as_bytes().iter().for_each(|&b| {
// 计数减少
cnt[b as usize] -= 1;
if cnt[b as usize] == -1 {
// 差异字符数增加
diff_char_count += 1;
}
});
// 覆盖子串结果信息
let (mut ans_len, mut ans_l) = (usize::MAX, usize::MAX);
// 开始滑动窗口
let s_len = s.len();
let (mut l, mut r) = (0, 0);
while r s_len {
// 计数增加
cnt[s.as_bytes()[r] as usize] += 1;
// 向右移动右边界后,可能该字符数量没有差异了
if cnt[s.as_bytes()[r] as usize] == 0 {
// 差异字符数减少
diff_char_count -= 1;
// 差异字符数减少后可能为0了
if diff_char_count == 0 {
// 向右滑动左边界,直到会有差异,取满足要求的最小串
while cnt[s.as_bytes()[l] as usize] > 0 {
cnt[s.as_bytes()[l] as usize] -= 1;
l += 1;
}
// 更新结果
if r - l + 1 ans_len {
ans_len = r - l + 1;
ans_l = l;
}
// 向右移动左边界,差异字符数增加
cnt[s.as_bytes()[l] as usize] -= 1;
l += 1;
diff_char_count += 1;
}
}
r += 1;
}
return if ans_l == usize::MAX {
"".to_string()
} else {
s[ans_l..ans_l + ans_len].to_string()
}
}
}
go:
func minWindow(s string, t string) string {
// 少于目标字符串中数量的字符数量
diffCharCount := 0
// 字符计数器
cnt := make([]int, 128)
// 初始化
for _, c := range t {
// 计数减少
cnt[c]--
if cnt[c] == -1 {
// 差异字符数增加
diffCharCount++
}
}
// 覆盖子串结果信息
ansLen, ansL := math.MaxInt32, -1
// 开始滑动窗口
sLen := len(s)
l, r := 0, 0
for r sLen {
// 计数增加
cnt[s[r]]++
// 向右移动右边界后,可能该字符数量没有差异了
if cnt[s[r]] == 0 {
// 差异字符数减少
diffCharCount--
// 差异字符数减少后可能为0了
if diffCharCount == 0 {
// 向右滑动左边界,直到会有差异,取满足要求的最小串
for cnt[s[l]] > 0 {
cnt[s[l]]--
l++
}
// 更新结果
if r-l+1 ansLen {
ansLen = r - l + 1
ansL = l
}
// 向右移动左边界,差异字符数增加
cnt[s[l]]--
l++
diffCharCount++
}
}
r++
}
if ansL == -1 {
return ""
} else {
return s[ansL : ansL+ansLen]
}
}
c++:
class Solution {
public:
string minWindow(string s, string t) {
// 少于目标字符串中数量的字符数量
int diffCharCount = 0;
// 字符计数器
int cnt[128];
memset(cnt, 0, sizeof(cnt));
// 初始化
const int tLen = t.length();
for (int i = 0; i tLen; ++i) {
if (--cnt[t[i]] == -1) {
// 差异字符数增加
++diffCharCount;
}
}
// 覆盖子串结果信息
int ansLen = INT_MAX, ansL = -1;
// 开始滑动窗口
const int sLen = s.length();
int l = 0, r = -1;
while (++r sLen) {
// 向右移动右边界后,可能该字符数量没有差异了
if (++cnt[s[r]] == 0) {
// 差异字符数减少后可能为0了
if (--diffCharCount == 0) {
// 向右滑动左边界,直到会有差异,取满足要求的最小串
while (--cnt[s[l++]] >= 0) {
// nothing
}
// 差异字符数增加
++diffCharCount;
// 更新结果
if (r - l + 2 ansLen) {
ansLen = r - l + 2;
ansL = l - 1;
}
}
}
}
return ansL == -1 ? "" : s.substr(ansL, ansLen);
}
};
python:
class Solution:
def minWindow(self, s: str, t: str) -> str:
# 少于目标字符串中数量的字符数量
diff_char_count = 0
# 字符计数器
cnt = collections.defaultdict(int)
# 初始化
for c in t:
# 计数减少
cnt[c] -= 1
if cnt[c] == -1:
# 差异字符数增加
diff_char_count += 1
# 覆盖子串结果信息
ans_len, ans_l = float("inf"), -1
# 开始滑动窗口
s_len = len(s)
l, r = 0, 0
while r s_len:
# 计数增加
cnt[s[r]] += 1
# 向右移动右边界后,可能该字符数量没有差异了
if cnt[s[r]] == 0:
# 差异字符数减少
diff_char_count -= 1
# 差异字符数减少后可能为0了
if diff_char_count == 0:
# 向右滑动左边界,直到会有差异,取满足要求的最小串
while cnt[s[l]] > 0:
cnt[s[l]] -= 1
l += 1
# 更新结果
if r - l + 1 ans_len:
ans_len = r - l + 1
ans_l = l
# 向右移动左边界,差异字符数增加
cnt[s[l]] -= 1
l += 1
diff_char_count += 1
r += 1
return "" if ans_l == -1 else s[ans_l: ans_l + ans_len]
java:
class Solution {
public String minWindow(String s, String t) {
// 少于目标字符串中数量的字符数量
int diffCharCount = 0;
// 字符计数器
final int[] cnt = new int[128];
// 初始化
final int tLen = t.length();
for (int i = 0; i tLen; ++i) {
if (--cnt[t.charAt(i)] == -1) {
// 差异字符数增加
++diffCharCount;
}
}
// 覆盖子串结果信息
int ansLen = Integer.MAX_VALUE, ansL = -1;
// 开始滑动窗口
final int sLen = s.length();
int l = 0, r = -1;
while (++r sLen) {
// 向右移动右边界后,可能该字符数量没有差异了
if (++cnt[s.charAt(r)] == 0) {
// 差异字符数减少后可能为0了
if (--diffCharCount == 0) {
// 向右滑动左边界,直到会有差异,取满足要求的最小串
while (--cnt[s.charAt(l++)] >= 0) {
// nothing
}
// 差异字符数增加
++diffCharCount;
// 更新结果
if (r - l + 2 ansLen) {
ansLen = r - l + 2;
ansL = l - 1;
}
}
}
}
return ansL == -1 ? "" : s.substring(ansL, ansL + ansLen);
}
}
非常感谢你阅读本文~
欢迎【点赞】【收藏】【评论】三连走一波~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子:https://le-yi.blog.csdn.net/ 博客原创~