启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情
该题是数组长度最小的子数组题型第三题。
题目来源
题目描述(困难)
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于
t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。 - 如果
s中存在这样的子串,我们保证它是唯一的答案。
示例1
输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
示例2
输入: s = "a", t = "a"
输出: "a"
示例3
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示
s和t由英文字母组成
进阶: 你能设计一个在 时间内解决此问题的算法吗?
题目解析
该题可以理解为字符串 t 中的每个字符都必须在字符串 s 截取的子字符串中出现,顺序不做要求,如下图所示:
从图中我们可以看到字符 A (蓝色边框)、字符 B (橙色边框)、字符 C (紫色边框)、其余字符(黑色边框)。
此时在字符串 s 中找到一段连续字符,包含字符串 t 中所有颜色的边框,所以此时符合条件的子字符串有: ADOBEC 、 BECODEBA 、 CODEBA 、 BANC 、 ADOBECODEBANC 等,你会发现所有字符串都包括字符 A 、 B 、 C ,且 数量大于等于 1 。
最终只需返回所有符合条件的子字符串中,长度最小的子字符串。
滑动窗口
首先,统计字符串 t 中,有几种不同的字符,每个字符数量为多少,可以将字符作为对象 need 的属性,属性值代表这个字符出现的次数,并且定义 length 存储字符串中存在多少种不同字符。
随后在右指针遍历字符串 s 的过程中,不断判断字符是否与字符串 t 中的某一个字符相同,如果相同,就将该字符作为属性存入对象 obj 中,该属性的初始值为 1 ,如果属性已存在,则该属性值加 1 。反之不做处理。
如果出现对象 need 和对象 obj 中相同属性的属性值相同,将满足条件数 sum 的值加 1 。直到出现 sum === length 时,右指针停止移动,并截取字符串 s 的 [left, right - 1] 作为子字符串,左指针开始移动,移动过程中,如果对象 obj 的属性存在该字符,此时便判断对象 nedd 和对象 obj 中该属性的属性值是否相同,如果相同,满足条件数 sum 的值减 1 ,左指针停止移动,右指针继续移动。
明确左右指针的用处:
- 左指针(
left):子字符串的左边界 - 右指针(
right):子字符串的右边界
区间范围为 [left, right)
注意:在窗口滑动过程中,只能有一个指针滑动。
代码
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function(s, t) {
let left = 0, right = 0, need = {}, obj = {}, sum = 0, length = 0, result = ""
// 遍历字符串 t ,存储需要需要在字符串 s 中寻找的字符以及对应数量,存储字符串 t 的种类数
for (const string of t) {
if (need[string]) {
need[string]++
}else {
need[string] = 1
length++
}
}
// 区间为左闭右开,所以采取小于判断条件
while (right < s.length) {
let c = s[right]
// 判断字符 c 是否是字符串 t 需要的
if (need[c]) {
// 判断字符 c 在对象 obj 存储了没
if (obj[c]) {
obj[c]++
}else {
obj[c] = 1
}
if (need[c] === obj[c]) {
sum++
}
right++
}else {
right++
}
// 此时左右指针切割下的子字符串包含字符串 t 中所有字符
while (length === sum) {
// 找到最短子字符串,并存储
result = result.length > right - left || result === "" ? s.substr(left, right - left) : result
if (obj[s[left]]) {
if (obj[s[left]] === need[s[left]]) {
sum--
}
obj[s[left]]--
}
left++
}
}
return result
};
- 时间复杂度:
- 空间复杂度:
如图:
代码(优化)
主要从代码的简洁度和可读性的角度优化
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function(s, t) {
let left = 0, right = 0, need = {}, obj = {}, sum = 0, length = 0, result = ""
for (const string of t) {
if (need[string]) {
need[string]++
}else {
need[string] = 1
length++
}
}
while (right < s.length) {
let c = s[right]
if (need[c]) {
// 优化
obj[c] = obj[c] ? obj[c] + 1 : 1
if (need[c] === obj[c]) {
sum++
}
}
right++
while (length === sum) {
result = result.length > right - left || result === "" ? s.substr(left, right - left) : result
// 优化
let start = s[left]
if (obj[start]) {
if (obj[start] === need[start]) {
sum--
}
obj[start]--
}
left++
}
}
return result
};
- 时间复杂度:
- 空间复杂度:
如图:
执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。