var findAnagrams = function (s, p) {
const map = p.split('').reduce((map, e) => {
if (map[e]) {
map[e]++
} else {
map[e] = 1
}
return map
}, {})
const res = []
let l = 0
let r = 0
while (r < s.length) {
// open
if (map.hasOwnProperty(s[r])) {
map[s[r]]--
}
r++
while (r - l >= p.length) {
// reduce
if (Object.values(map).every(val=>val==0)) {
res.push(l)
}
if (map.hasOwnProperty(s[l])) {
map[s[l]]++
}
l++
}
}
return res
};
学习过程记录
模板
要点
left 和 right 两个指针
外层 while 代表是否需要扩张窗口, 当右边指针到边之前都是 right++
里层 while 代表是否需要缩小窗口, 当窗口内的数据不符合要求 left++
当窗口内的数据符合要求的时候, 记录下来窗口长度, 并输出记录过的最短长度.
来一题: 76. Minimum Window Substring
思路
按照上面的模板写出来并不难, 但这题的优化策略如下
用 start 和 end 参数分别记录 最短窗口位置的起点和窗口长度. 只有当需要更新最短窗口的时候才更新他们. 这样可以避免多次截取字符串来拿到窗口内的字符串.
如何高效的判断窗口内的字符串是否符合要求? 第一思路自然是for循环某个字符串, 查看他的每一个字符是否在另一个里面, 但这样是
O(n^2)
了, 我们可以将 目标字符串 t 转化成map, 然后滑动窗口每次有新的字符串进入,map[key]--
, 如果窗口收缩的时候,map[key]++
, 这样时时刻刻更新map, 最后我们只需要校验map中的每个值是否大于0即可高效的校验当前窗口内的字符串是否符合要求.答案
再来一题: 567. Permutation in String
思路
基本上和上面的是一模一样, 基本抛弃原先的多次遍历循环的解法了, 太低效了.
维护一个滑动的窗口, 这个窗口由 l, r 作为左节点和右节点进行维护.
针对
s1
, 由于它是可以任意排序的, 因此对它生成一个map用于记录各个字母出现的次数.滑动窗口什么时候扩张? 当r节点小于s2字符长度,
滑动窗口什么时候收缩? 当窗口长度小于 s1.length就收缩
滑动窗口扩张的时候, 拿到推入的那个字符串, 如果存在于 map, 则 map[key]--
滑动窗口收缩的时候, 拿到推出的那个字符串, 如果存在于 map, 则 map[key]++
收缩的同时, 校验map是否每个字符串都为 0, 如果为0, 则直接return true.
在最后return false, 即找不到符合条件的字符串.
答案
再来一题: 438. Find All Anagrams in a String
思路
和上面的基本一致, 维护一个滑动窗口, 有符合条件直接把当前坐标推入 res 数组中.
答案
3. Longest Substring Without Repeating Characters
思路
同样是滑动窗口算法, 不过位置需要调转一下.
答案
424. Longest Repeating Character Replacement
思路
这题我也不知道为什么对了, 反正框架写出来修修改改就是对的.
答案
Reference
我写了套框架,把滑动窗口算法变成了默写题