滑动窗口主要用来解决寻找满足一定条件的连续区间的性质-如长度问题。由于区间连续,因此当区间变化时,可以利用旧有的计算结果进行剪枝,从而减少计算量。例如“满足xx的最x的子数组”问题就可以用滑动窗口解决。
滑动窗口有两种类型,分别是固定大小和非固定大小。
非固定大小类型的问题,以解决最长无重复子树组举例:
非固定大小滑动窗口的基本原理:
- 初始化left = 0, right = 0
- 向右移动right
- 判断窗口内的连续元素是否满足条件
- 如果满足,再判断是否需要更新最优解,如果需要则更新
- 如果不满足,则向右移动left到最合适的位置
知道原理来,实现起来也并不复杂:
function maxLength( arr ) {
let maxLen = 0;
let map = new Map();
// 用双指针模拟一个滑动窗口,窗口向右滑动
for(let left = 0, right = 0; right < arr.length; ++right){
// 遇到重复数字
if(map.has(arr[right])){
// 因为有可能遇到的重复数字的位置在left前面,比较当前left的位置与重复数字的位置
// 如当输入是abcba,因为b重复了,所以left已经移动到了c
// 当right移动到a时,虽然a重复了,但是left已经移动到a后面了
left = Math.max(left, map.get(arr[right]) + 1);
}
map.set(arr[right], right);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
再来一道leetcode的题目1208. 尽可能使字符串相等
var equalSubstring = function(s, t, maxCost) {
let cost = 0;
let maxLen = 0;
for(let left = 0, right = 0; right < s.length; ++right){
// 计算当前字符进行转换需要的开销
const rightCost = Math.abs(s.charCodeAt(right) - t.charCodeAt(right));
// 如果开销总和大于最大预算,则将左指针往右移,并且从开销中减去左指针对应的开销
// 由于右指针指向的字符转换开销可能比较大,左指针需要向右移动多个位置
while(cost + rightCost > maxCost){
cost -= Math.abs(s.charCodeAt(left) - t.charCodeAt(left));
++left;
}
cost += rightCost;
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
};
固定大小的滑动窗口问题,以1456.定长子串元音中的最大数目为例:
固定窗口就不需要用到双指针了,只需要right指针,left指针可以轻易地通过right - k计算出来
思路如下:
- 初始化right指针, 并构造大小为
k的滑动窗口 - right指针指向位置
k,窗口开始滑动 - 将最左边的元素移出窗口,可通过
right - k计算其位置,并做相应的处理 - 将right指针指向的元素添加至窗口,判断是否需要更新最优解,如果需要则更新,否则继续
/**
* @param {string} s
* @param {number} k
* @return {number}
*/
var maxVowels = function(s, k) {
let curCount = 0;
let maxCount = 0;
if(s.length < k || k === 0) return maxCount;
// 构造大小为k的窗口
for(let right=0; right<k; right++){
if(isVowel(s[right])){
++curCount;
maxCount = Math.max(maxCount, curCount);
}
}
// 窗口向右移动
for(let right=k; right<s.length; right++){
// 将左边界元素移除
if(isVowel(s[right-k])) --curCount;
// 将右边界元素移入
if(isVowel(s[right])){
++curCount;
// 判断是否需要更新最优解
maxCount = Math.max(maxCount, curCount);
}
}
// 返回最优解
return maxCount;
};
// 判断是否是元音字母
var isVowel = (ch) => {
if(['a', 'e', 'i', 'o', 'u'].includes(ch)) return true;
return false;
}
使用滑动窗口解决的问题通常是暴力解法的优化,掌握这一类问题最好的办法就是练习。leetcode上已经有滑动窗口问题系列了,滑动窗口问题系列。