题目
题目来源:力扣(LeetCode)
给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
思路分析
滑动窗口
用 left, rigth 表示滑动窗口的左边界和右边界,通过改变 left, right 来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度right - left + 1,这些长度中的最小值就是要求的结果。
- 我们在字符串 S 中使⽤双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left,
right] 称为⼀个「窗⼝」。 - 我们先不断地增加 right 指针扩⼤窗⼝ [left, right],直到窗⼝中的字符串符合要求(包含了 t 中的
所有字符) - 我们停⽌增加 right,转⽽不断增加 left 指针缩⼩窗⼝ [left, right],直到窗⼝中的字符串不再符合
要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新⼀轮结果 - 重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function(s, t) {
// 需要的
let need = {};
// 窗口中的字符
let window = {};
for (let a of t) {
// 统计需要的字符
need[a] = (need[a] || 0) + 1;
}
// 左右指针
let left = 0,
right = 0;
let valid = 0;
// 最小覆盖子串的起始索引及长度
let start = 0,
len = Number.MAX_VALUE;
while (right < s.length) {
// 即将移入窗口的字符
let c = s[right];
// 右移窗口
right++;
if (need[c]) {
// 当前字符在需要的字符中,则更新当前窗口统计
window[c] = (window[c] || 0) + 1;
if (window[c] == need[c]) {
// 当前窗口和需要的字符匹配时,验证数量增加1
valid++;
}
}
// 当验证数量与需要的字符个数一致时,就应该收缩窗口了
while (valid == Object.keys(need).length) {
// 更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
//即将移出窗口的字符
let d = s[left];
// 左移窗口
left++;
if (need[d]) {
if (window[d] == need[d]) {
valid--;
}
window[d]--;
}
}
}
return len == Number.MAX_VALUE ? "" : s.substr(start, len);
};