Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。
前言
每天一道算法题,死磕算法
这是leetcode上的第76道题目76. 最小覆盖子串
题目
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解析
一看这个是子串问题,就要使用一个新的policy---滑动窗口
首先先理解一下什么是滑动窗口?
不知道大家在学网络原理的时候还记得滑动窗口协议么?
滑动窗口协议(Sliding Window Protocol),该协议是 TCP协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。
滑动窗口算法其实和这个是一样的,只是应用场景不一样。
简而言之,滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作。
什么时候使用滑动窗口算法?
滑动窗口是双指针的一种,是专门用来解决子串问题的。
模板
什么都没有模板香呀,模板在手,天下我有
1.先定义两个对象,一个记录窗口中的字符,一个记录需要凑齐的字符
let window = {},need = {};
for(let c of t){
if(need[c]){
need[c] = 0;
}
need[c]++;
}
2.初始化窗口的两端,并进行滑动,valid是计算当前子串包含的符合条件字符的个数
let left = 0,right =0,valid = 0;
// 开始滑动
while(right<Object.keys(s).length){}
3.窗口右侧开始向右滑动
// 开始滑动
while(right<Object.keys(s).length){
let c = s[right];
right++;
// 进行一系列数据操作
}
4.当valid和need的length一致的时候,就说明窗口中包含了所有需要的字符,此时,left开始缩小
while(valid === Object.keys(need).length){
const d = s[left];
left++;
// 进行一系列数据操作
}
所以最终我们的框架就是
var slidingWindow = function(s,t){
let window = {},need = {};
for(let c of t){
if(need[c]){
need[c] = 0;
}
need[c]++;
}
let left = 0,right =0,valid = 0;
// 开始滑动
while(right<Object.keys(s).length){
let c = s[right];
right++;
// 进行一系列数据操作
while(判断是否需要缩小){
const d = s[left];
left++;
// 进行一系列数据操作
}
// 符合条件的时候
}
}
有了模板以后就相当于有了一把利剑
题解
然后再回来看我们的题目,其实就是让我们求出滑动窗口滑动到结束的时候,窗口中的字符串,我们模板只是管滑动,但并没有记录窗口,所以我们定义两个属性start和len,来表明窗口的开始位置和长度
var minWindow = function(s,t){
let need ={},window={};
for(let c of t){
if(!need[c]){
need[c]=0;
}
need[c]++;
}
let left=0,right=0,valid=0;
let start=0,len=Infinity;
while(right<s.length){
let c = s[right];
right++;
if(need[c]){
if(!window[c]){
window[c]=0;
}
window[c]++;
if(window[c]===need[c]){
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 === Infinity? "": s.substr(start,len);
}
直接搞定
总结
滑动窗口这个代码的确很长很长,只有理解了原理,才能方便记忆,这是一个把抽象变具体的过程