这不是一个给一道题目而后告诉你题解的系列,而是对于一系列题目进行分类,找出他们解题规律并得出大体框架代码的文章。吃透解一系列题目的规律比会解单个题目有用的多,毕竟你总会遇到没刷过的题。算法
你们对于滑动窗口应该不陌生,在 TCP 协议中就有这个概念的出现,用于控制网络流量,避免拥塞发生。数组
在算法中这个思想也是相似的,多用于解决在一段连续的区间中寻找知足条件的问题,好比说给定一个字符串,寻找出无重复字符的最长子串。该思路主要应用于数组及字符串的数据结构中。markdown
滑动窗口主要思路是维护一对指针,在必定条件内右移右指针扩大窗口大小直到窗口内的解不知足题意,此时咱们须要根据状况移动左指针,重复移动左右指针的操做并在区间内求解,直到双指针不能再移动。网络
以 寻找出无重复字符的最长子串 题目为例,根据上述的思路解题就会很方便:数据结构
var lengthOfLongestSubstring = function(s) {
// 用于存储指针移动过程当中的值
let map = {}
// 双指针
let left = 0
let right = 0
// 结果
let count = 0
// 指针移动终止条件
while (right < s.length) {
const char = s[right]
// 根据题意咱们须要寻找不重复的最长子串
// 当 char 出现时咱们须要移动左指针到重复字符的下一位
if (char in map) {
left = Math.max(left, map[char] + 1)
}
// 求解
count = Math.max(count, right - left + 1)
// 移动右指针并存下索引
map[char] = right++
}
return count
};
复制代码
此题为高频题,你们务必掌握框架
根据上题咱们能够得出一个滑动窗口解题的大体框架的伪代码,oop
let left = 0
let right = 0
while (right < size) {
获取当前索引数据
right++
数据更新等操做
while (窗口须要缩小) {
left++
数据移除等操做
}
}
复制代码
框架中须要变化的几点以下:ui
接下来咱们根据这个框架代码来试着解决几道题目。spa
解题思路:指针
s
时移动左指针缩小窗口,此时须要更新累加值及咱们须要的解var minSubArrayLen = function(s, nums) {
// 定义双指针
let left = 0
let right = 0
// 求解须要用到的变量
let length = Infinity
let sum = 0
// 指针移动终止条件
while (right < nums.length) {
// 获取当前索引数据
sum += nums[right]
// 缩小窗口条件
while (sum >= s) {
// 求解
length = Math.min(length, right - left + 1)
// 缩小窗口
sum -= nums[left++]
}
// 扩大窗口
right++
}
return length === Infinity ? 0 : length
};
复制代码
这道题目是 Leetcode 的第 209 题,答案能够说除了小部分的微调以外,基本套用了框架代码。后续的题目你们能够继续跟着这个思路解题,快速掌握经过滑动窗口来解题的套路。
解题思路:
p
中的字符出现次数var findAnagrams = function(s, p) {
if (!s.length || !p.length || s.length < p.length) return []
// 求解须要用到的变量
const map = {}
const result = []
// 定义双指针
let left = 0, right = 0
// 把字符串 p 中的字符经过 hash 存储起来
for (let i = 0; i < p.length; i++) {
const char = p[i]
if (!(char in map)) {
map[char] = 0
}
map[char] += 1
}
// 指针移动终止条件
while (right < s.length) {
const char = s[right]
// map 中存在字符就移动右指针
if (map[char] > 0) {
map[char] -= 1
right++
// 不然判断左指针所指向的字符是否存在 map 中
} else if (map[s[left]] >= 0) {
map[s[left]] += 1
left++
// 不存在的话把左右指针所有挪到下一位
} else {
left = right += 1
}
// 存储正确解
if (right - left === p.length) {
result.push(left)
}
}
return result
};
复制代码
这道题目和以前的 「找到字符串中全部字母异位词」思路很相似:
t
中的字符出现次数var minWindow = function(s, t) {
// 定义双指针
let left = 0, right = 0
// 求解须要用到的变量
let length = Infinity
let map = {}
// 遇到 t 中存在的字符时更新 match,注意 t 中存在的字符可能在 s 中出现屡次
// 所以并非每次都须要更新 match
let match = 0
// 记录最短子串开始位置,不能用 left
let start = 0
// 把字符串 t 中的字符经过 hash 存储起来
for (let i = 0; i < t.length; i++) {
const char = t[i]
if (!(char in map)) {
map[char] = 0
}
map[char] += 1
}
// 指针移动终止条件
while (right < s.length) {
const char = s[right]
// 右指针移动时更新数据
if (char in map) {
map[char] -= 1
if (map[char] >= 0) match += 1
}
// 缩小窗口条件
while (match === t.length) {
// 寻找到更佳解,保存数据
if (length > right - left + 1) {
length = right - left + 1
start = left
}
// 移动左指针而且更新数据
const char = s[left++]
if (char in map) {
if (map[char] === 0) match -= 1
map[char] += 1
}
}
// 移动右指针
right++
}
return length === Infinity ? '' : s.substring(start, start + length)
};
复制代码
通过上面几道题目的练习,你们应该能看出滑动窗口的思路多用于解决数组及字符串中子元素的问题。