Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
1.题目
76. 最小覆盖子串 给你一个字符串
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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
二、思路分析:
- 如果没有什么思路的话首先想到这道题的解法:
暴力解法,通过两层循环不断地更新子串大小进行匹配,但是这种解法的时间复杂度会很高,提交出去的代码会超时。 - 第二种解法:看到子串问题想到了
滑动窗口的解法,这道题的滑动窗口的思路是利用左右双指针进行题目的求解,它的思路是:
- 初始化左右指针在字符串的起始位置,利用对象targets存储目标串的每个字符出现的次数
- 右指针一直循环到字符串的最右侧为止,右指针每进入一个字符就判断该字符出没出现在目标字符中,如果出现则将该字符存入滑动窗口window对象中,随后判断window对象中该字符数量是否与targets中存储的值相等,如果相等则valid值自增一次
- 右指针移动过后判断window对象和target对象的值是否相等,如果相等则可以开始进行缩小窗口的操作,先记录下此覆盖子串的起始位置以及长度以便后续的判断,如果此覆盖子串比先前的覆盖子串长度更小则取代上一个覆盖子串。
- 接着在缩小滑动窗口的操作中不断地移出左指针指向的字符,如果不是targets对象里面的字符则继续缩小滑动窗口,否则在进行targets与window对象该字符数值的判断并对window对象和valid值进行增减操作,随后跳出缩小滑动窗口循环。
- 接着再次进行右指针扩大滑动窗口的循环,在进行2、3、4步的操作直到循环结束。
三、代码:
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function(s, t) {
//首先把字符串的字母以及数量存入数组中
let targets = {}
let window = {}
for(let target of t){
//有相同字符数量加1 没有数量为1
targets[target] = (targets[target] || 0)+1
}
//定义左右指针
let left = right = 0
let valid = 0
let start = 0
let len = Number.MAX_SAFE_INTEGER
while(right < s.length){
//c是移入的字符,右移窗口操作
let c = s[right]
right++
//如果移入的字符符合目标串则加入window对象
if(targets[c]){
window[c] = (window[c] || 0)+1
//特定字符的数量完全匹配则valid加1
if(window[c] == targets[c]){
valid++;
}
}
//判断左指针是否需要收缩根据valid判断
while(valid == Object.keys(targets).length){
//判断最小覆盖子串,记录最小覆盖子串的起始位置
if(right - left <len){
start = left
len = right - left
}
//移出字符以及进行左指针右移操作
let d = s[left]
left++
//再次更新window以及valid数据
if(targets[d]){
//判断特定字符数量是否完全匹配,如果完全匹配valid-1
if(targets[d] == window[d]) valid--
window[d]--
}
}
}
return len == Number.MAX_SAFE_INTEGER?"":s.substr(start,len)
};
四、总结:
滑动窗口的思想尤其适合这种子串问题,以后遇到这种问题应该第一时间想到这种思路进行求解而不是使用暴力解法求解问题。这是刷题打卡的第6题希望自己的能力能够越来越好0.0