滑动窗口总结
- 抓住题目条件,构造滑动窗口
- 使用左left右right(两个不必每次都同时存在,有的题只需要一个指针)指针
- right右移,遇到不符合题目条件的需要保留计算结果,并移动left指针,保证窗口(left与right之间的)内的元素符合题目条件
滑动窗口代码模板
var slip = function(s, p) {
let pLen = p.length, sLen = s.length;
if(pLen > sLen)
return []
let result = []
let left = 0, right = 0
let window = {}, need = {}
let valid = 0
for(i of p){
if(need[i] === undefined)
need[i] = 0
need[i]++
window[i] = 0
}
let needSize = Object.keys(need).length
while(right < sLen){
const c = s[right]
right++
if(need[c] !== undefined){
window[c]++
...
}
if(valid === needSize){
result.push(left)
}
while(right - left >= pLen){
const d = s[left]
left++
if(window[d] !== undefined){
...
}
}
}
return result
};
举例
- 给定一个字符串
s ,请你找出其中不含有重复字符的 最长子串 的长度。
解题思路:
- 使用map保存进入窗口元素下标
- right指针右移,当它不在map中时,代表不与之前元素重复,right继续右移
- 当它在map中存在时,代表该元素之前出现过,此时计算righ-left+1即当前无重复的子串长度,与之前的max做对比,取最大的,并且left指针右移,直到窗口中没有重复元素。
var lengthOfLongestSubstring = function(s) {
let len = s.length;
let left = 0, max = 0;
let map = new Map();
for(let right = 0; right < len; right++) {
if(map.has(s[right]) && map.get(s[right]) >= left) {
left = map.get(s[right]) + 1;
}
max = Math.max(max, right - left + 1);
map.set(s[right], right)
}
return max;
};
- DNA序列 由一系列核苷酸组成,缩写为 'A', 'C', 'G' 和 'T'.。
- 例如,"ACGAATTCCG" 是一个 DNA序列 。在研究 DNA 时,识别 DNA 中的重复序列非常有用。
- 给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串)。你可以按 任意顺序 返回答案。
解题思路:
- 该题滑动窗口长度已固定为10,需要找出出现次数不止一次的长度为10的序列
- 使用map存储序列,key为序列,value为出现次数
- 该题只需要1个指针,滑动窗口长度为10,窗口组成的字符串存在于map中则map.get(str)+1,否则map.set(str, 1)
- 最后遍历map,统计value大于1的
let len = s.length;
let map = new Map();
let res = [];
for(let i = 0;i < len;i ++) {
let str = s.substr(i, 10)
let l = str.length;
if(l === 10) {
if(map.get(str)) {
map.set(str, map.get(str) + 1)
} else {
map.set(str, 1)
}
}
}
for(let k of map.keys()) {
if(map.get(k) > 1) {
res.push(k);
}
}
return res;
};
- 给你一个字符串 s 和一个整数 k 。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。
- 在执行上述操作后,返回包含相同字母的最长子字符串的长度。
解题思路:
- 该题的滑动窗口长度包括重复字符出现的最大次数max+k个非重复字符的长度
- 使用长度为26的数组,保存每个字母重复出现的次数
- right指针右移,数组中代表s[right]字母的元素个数++
- 判断max和数组中代表s[right]字母的元素谁大,即保存最大重复元素个数
- 当right-left+1(即滑动窗口长度) - max(最大重复元素个数) > k时,说明不重复元素个数>k,此时需要将left指针右移保证窗口内的不重复元素<=k,并且将数组中该重复元素数量-1,最后right-left滑动窗口长度即为结果
var characterReplacement = function(s, k) {
let len = s.length;
let num = new Array(26).fill(0);
let right = 0, left = 0, max = 0;
while(right < len) {
num[s[right].charCodeAt() - 65]++;
max = Math.max(max, num[s[right].charCodeAt() - 65]);
if(right - left + 1 - max > k) {
num[s[left].charCodeAt() - 65]--;
left++;
}
right++;
}
return right - left;
};
- 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
- 异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
解题思路:
- 该题滑动窗口长度为p字符串长度
- 使用need保存p中字母以及字母个数,needSize代表需要的字母,窗口固定为p的长度,valid保存满足的字母个数,遍历时如果need中有此字母。window也有,并且字母的个数一致,则valid++。
- 当窗口里的字母以及字母个数满足need时,将该子串push到结果数组中
- 当窗口长度>p的长度时,移动left指针并从窗口中去除左侧字母。
var findAnagrams = function(s, p) {
let pLen = p.length, sLen = s.length;
if(pLen > sLen)
return []
let result = []
let left = 0, right = 0
let window = {}, need = {}
let valid = 0
for(i of p){
if(need[i] === undefined)
need[i] = 0
need[i]++
window[i] = 0
}
let needSize = Object.keys(need).length
while(right < sLen){
const c = s[right]
right++
if(need[c] !== undefined){
window[c]++
if(window[c] === need[c])
valid++
}
if(valid === needSize){
result.push(left)
}
while(right - left >= pLen){
const d = s[left]
left++
if(window[d] !== undefined){
if(window[d] === need[d])
valid--
window[d]--
}
}
}
return result
};
- 几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。
- 每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。
- 你的点数就是你拿到手中的所有卡牌的点数之和。
- 给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。
解题思路:
- 该题的窗口有些隐蔽,只能从开头和结尾拿卡牌,求拿的最大点数,其实可以转化为求n-k窗口内卡牌最小点数。
- 因此只需要right指针,每次记录n-k窗口内的点数的最小值即可。
var maxScore = function(cardPoints, k) {
const n = cardPoints.length;
const windowSize = n - k;
let sum = 0;
for (let i = 0; i < windowSize; ++i) {
sum += cardPoints[i];
}
let minSum = sum;
for (let i = windowSize; i < n; ++i) {
sum += cardPoints[i] - cardPoints[i - windowSize];
minSum = Math.min(minSum, sum);
}
let totalSum = 0;
for (let i = 0; i < n; i++) {
totalSum += cardPoints[i];
}
return totalSum - minSum;
}
- 一位老师正在出一场由 n 道判断题构成的考试,每道题的答案为 true (用 'T' 表示)或者 false (用 'F' 表示)。老师想增加学生对自己做出答案的不确定性,方法是 最大化 有 连续相同 结果的题数。(也就是连续出现 true 或者连续出现 false)。
- 给你一个字符串 answerKey ,其中 answerKey[i] 是第 i 个问题的正确结果。除此以外,还给你一个整数 k ,表示你能进行以下操作的最多次数:
- 每次操作中,将问题的正确答案改为 'T' 或者 'F' (也就是将 answerKey[i] 改为 'T' 或者 'F' )。
- 请你返回在不超过 k 次操作的情况下,最大 连续 'T' 或者 'F' 的数目。
解题思路:
- 与替换后的最长重复字符思路相似,非重复字符个数不超过k个,该题可以拆成改T或者改F。
- right指针右移,如果与T/F不一致则sum++,当sum>k时,代表窗口内元素不符合条件,则left指针右移,sum也需要做相应清除。
var maxConsecutiveAnswers = function(answerKey, k) {
return Math.max(maxConsecutiveChar(answerKey, k, 'T'), maxConsecutiveChar(answerKey, k, 'F'));
}
const maxConsecutiveChar = (answerKey, k, ch) => {
const n = answerKey.length;
let ans = 0;
for (let left = 0, right = 0, sum = 0; right < n; right++) {
sum += answerKey.charAt(right) !== ch ? 1 : 0;
while (sum > k) {
sum -= answerKey[left++] !== ch ? 1 : 0;
}
ans = Math.max(ans, right - left + 1);
}
return ans;
};