[算法整理]滑动窗口

106 阅读2分钟

滑动窗口

滑动窗口的本质其实就是双指针,使用leftright指针构成一个窗口

一般来说我们不固定窗口的大小,left = 0,right = 0

right指针:向右移动

left指针:当[left,right)里面满足了条件,这时候我们的left就需要向右移动,直到不再满足条件

再次移动right,直到再次满足条件,然后一直循环,直到无法构成窗口

滑动窗口解题模板

var minWindow = function (s:string, t:string) : string{
    // need是target的集合
    const need = new Map<string, number>();
    // window是当前选中框的集合
    const window = new Map<string, number>();
    for (let value of t) {
        if (need.has(value)) {
            need.set(value, need.get(value) + 1)
        } else {
            need.set(value, 1)
        }
    }

    // 设置两个边界,left和right
    let left = 0;
    let right = 0;

    while (right < s.length) {
        let d = s[right];
        right++
        //进行窗口内数据的更新
        
        
        //当满足条件,进行收缩
        while (valid === need.size) {
            if (right - left < len) {
                start = left
                len = right - left
            }
            // 开始收缩
            let e = s[left]
            left++
            //进行窗口的数据更新
            
            
            //
        }
    }
    
    return ....
};

个人觉得,滑动窗口一般都是给你两个字符串,然后在S中找T啥的

一定要注意好收缩的条件,就是啥时候收缩

滑动窗口例题

最小覆盖字串

链接:76. 最小覆盖子串 - 力扣(LeetCode)

解题思路:

  1. 在字符串s中使用双指针,初始化left = 0,right = 0
  2. 首先我们不断增加right,直到满足条件,我们就不在增加left
  3. left这时候继续向右,这样直到不在满足条件为止,这时候再继续,向右移动right
  4. 就这样一直重复,直到right到达字符串s的尽头
var minWindow = function (s, t) {
    // need是target的集合
    let need = new Map();
    // window是当前选中框的集合
    let window = new Map();
    for (let value of t) {
        if (need.has(value)) {
            need.set(value, need.get(value) + 1)
        } else {
            need.set(value, 1)
        }
    }

    // 设置两个边界,left和right
    let left = 0;
    let right = 0;
    // valid意思是满足的情况
    let valid = 0;
    // 判断是不是满足条件,需要收缩
    // 记录最小
    let len = Number.MAX_VALUE
    let start = 0
    while (right < s.length) {
        let d = s[right];
        right++
        if (need.has(d)) {
            window.set(d, window.get(d) ? window.get(d) + 1 : 1)
            if (window.get(d) === need.get(d)) {
                valid++
            }
        }
        while (valid === need.size) {
            if (right - left < len) {
                start = left
                len = right - left
            }
            // 开始收缩
            let e = s[left]
            left++
            if (need.has(e)) {
                if (window.get(e) === need.get(e)) {
                    valid--;//valid不符合条件
                }
                window.set(e, window.get(e)-1)
            }
        }
    }
    
    return len === Number.MAX_VALUE ? '' : s.slice(start, start+len)
};

最长不包含重复字符的子字符串

链接:剑指 Offer 48. 最长不含重复字符的子字符串 - 力扣(LeetCode)

思路:

  1. 依然是使用滑动窗口,维护两个指针leftright
  2. 维护一个map,用来判断是不是已经重复
  3. right先向右移动,当map中的已经有值>1,也就是,已经有了重复了,进行收缩
  4. left收缩,因为返回的值是长度,所以我们时刻关注长度问题
  5. 一直进行下去,直到right到达尽头

代码:

function lengthOfLongestSubstring(s: string): number {
    // 滑动窗口
    let left = 0;
    let right = 0;
    let res = 0;
    // 维护一个map
    const window = new Map();
    while(right<s.length){
        let c = s[right];
        right++
        if(window.has(c)){
            window.set(c,window.get(c)+1)
        }else{
            window.set(c,1)
        }
        // 如果这个值,他已经大于1,那么就说明,需要收缩
        while(window.get(c)>1){
            let d = s[left]
            left++
            window.set(d,window.get(d)-1)
        }
        res = Math.max(res,right-left)
    }
    return res
};

字符串的排列

链接:567. 字符串的排列 - 力扣(LeetCode)

题解:

  1. 同样的采用滑动窗口,解题思路和第一题类似
  2. 但是,这边变简单了,因为他只要求我们判断是不是
  3. 也就是我们只需要判断valid === need.size就好了,为真,那么肯定存在
  4. 但是需要注意收缩的条件
  5. 题目说,要想s1s2的子串,那么也就意味着,他们的长度是肯定大于等于s1的,假如小于,肯定不存在了

代码:

function checkInclusion(s1: string, s2: string): boolean {
    // 判断两个
    const need = new Map<string,number>();
    const window  = new Map<string,number>()
    let left = 0;
    let right = 0;
    let start = 0;
    let valid = 0;

    for(let value of s1){
        if(need.has(value)){
            need.set(value,need.get(value)+1) 
        }else{
            need.set(value,1)
        }
    }
    // 这时候我们进行区间
    while(right<s2.length){
        let c = s2[right]
        right++
        if(need.has(c)){
            if(window.has(c)){
                window.set(c,window.get(c)+1)
            }else{
                window.set(c,1)
            }
            // 判断是不是满足条件
            if(need.get(c) === window.get(c)){
                valid++
            }
        }
        // 判断是不是都符合条件
        while(right-left >= s1.length){
            // 成立的条件是啥
            if(need.size === valid){
                return true
            }
            // 那么就可以收缩
            let d = s2[left]
            left++;
            // 判断need里面是不是需要
            if(need.has(d)){
                // 那么就说明不在满足情况
                // 并且刚好满足条件
                if(need.get(d) === window.get(d)){
                    valid--
                }
                // 那么相对应的,那么window的值减一
                window.set(d,window.get(d)-1);
            }
        }
        
        
    }
    return false
};

参考链接:

我写了首诗,把滑动窗口算法变成了默写题 :: labuladong的算法小抄 (gitee.io)