【算法、动态规划】滑动窗口专项训练

滑动窗口专项训练

更新记录

2024.5.17 第一次更新
2024.7.15 第二次更新,从第三道实战训练题开始

一般解题步骤

待更新

1.定义需要维护的变量

可能是:

  • 哈希表(map或set)
  • 最短/最长长度

2.初始化滑动窗口,startend一般都初始化为0(如果是双指针两边夹的情况,一般都是贪心?)

解题模板

由于自己的理解还不够深刻,这里借鉴大佬的思路

https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters/solutions/879777/hua-dong-chuang-kou-zhen-di-jian-dan-yi-73bii

大佬的是python的版本

class Solution:
    def problemName(self, s: str) -> int:
        # Step 1: 定义需要维护的变量们 (对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
        x, y = ..., ...

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end in range(len(s)):
            # Step 3: 更新需要维护的变量, 有的变量需要一个if语句来维护 (比如最大最小长度)
            x = new_x
            if condition:
                y = new_y

            '''
            ------------- 下面是两种情况,读者请根据题意二选1 -------------
            '''
            # Step 4 - 情况1
            # 如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否超过限定长度 
            # 如果超过了,窗口左指针前移一个单位保证窗口长度固定, 在那之前, 先更新Step 1定义的(部分或所有)维护变量 
            if 窗口长度大于限定值:
                # 更新 (部分或所有) 维护变量 
                # 窗口左指针前移一个单位保证窗口长度固定

            # Step 4 - 情况2
            # 如果题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            # 如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            # 在左指针移动之前更新Step 1定义的(部分或所有)维护变量 
            while 不合法:
                # 更新 (部分或所有) 维护变量 
                # 不断移动窗口左指针直到窗口再次合法

        # Step 5: 返回答案
        return ...

按照大佬是思路,改成JS的版本

/**
 * @param {string} s
 */
var problemName = function(s) {
    // Step 1: 定义需要维护的变量们 (对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
	let [x, y] = ...
  
  // Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
    let st = 0;
  	for(let end = 0; end < s.length; s++){
        //Step 3: 更新需要维护的变量, 有的变量需要一个if语句来维护 (比如最大最小长度)
        x = new_x;
      	if(condition){
          y = new_y;
        }
      // Step 4 - 情况1
            // 如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否超过限定长度 
            // 如果超过了,窗口左指针前移一个单位保证窗口长度固定, 在那之前, 先更新Step 1定义的(部分或所有)维护变量 
            if(窗口长度大于限定值){
               // 更新 (部分或所有) 维护变量 
                // 窗口左指针前移一个单位保证窗口长度固定
            }
              
            // Step 4 - 情况2
            // 如果题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            // 如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            // 在左指针移动之前更新Step 1定义的(部分或所有)维护变量 
            while(不合法){
            	//更新 (部分或所有) 维护变量 
              //不断移动窗口左指针直到窗口再次合法
            }           
    }
  // Step 5: 返回答案
  return ...
};

实战训练

一、无重复字符的最长子串

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成

code:

不用map,用set和单纯数组都行,数组的话就用include方法来查看字母是否已经存在于数组中,但是最好还是不要数组了,数组里面删除一个元素会很麻烦,主要还是哈希+滑动窗口的思想。

用set的话会简单一点点

带注释版本

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    if(s.length==0) return 0;
    //无重复字符的最长子串
    //ex
    // abcabcbb
    // abc -> maxLen = 3
    //滑动窗口
    //1.定义需要维护的变量
    let maxLen = -1;
    let hash = new Map();
    //2.定义窗口的首尾端,然后滑动窗口
    let st = 0;
    for(let end = 0; end < s.length; end++){//第一次debug是end++写成s++了
        //维护变量
        let c = s[end];
        
        //情况2:窗口可变,检查窗口是否合法,不合法就调整st指针直至合法  
        //在该题目中,不合法指的是,字符串中出现重复字符 
        if(hash.has(c)){//c字符不是第一次出现,窗口不合法
            //只要连续移动字符,直到新的窗口的字符中不包含第一次出现的c字符位置
            while( st < end && s[st] != c){
                hash.delete(s[st]);//第二次debug,这一句和下面一句的顺序反了,如果先++在delet,那么相当于delet的是下一个字符,第一个字符永远都不会被移除
                st++;
            } 
            //此时的st应该位于第一个窗口的第一个c字符处
            st++;
            //现在窗口合法了
        }else{//第一次出现
            hash.set(c, 1);
        }

        //窗口的长度为end - st + 1 (左闭右闭区间)
        //如果是左闭右开区间,就是end - st
        maxLen = Math.max(maxLen, end - st + 1);
    }
    return maxLen;
};

不带注释

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    if(s.length==0) return 0;
    let maxLen = -1;
    let hash = new Map();
    let st = 0;
    for(let end = 0; end < s.length; end++){
        let c = s[end];    
        if(hash.has(c)){
            while( st < end && s[st] != c){
                hash.delete(s[st]);
                st++;
            } 
            st++;
        }else{
            hash.set(c, 1);
        }
        maxLen = Math.max(maxLen, end - st + 1);
    }
    return maxLen;
};

二、找到字符串中所有字母异位词

438. 找到字符串中所有字母异位词

给定两个字符串 sp,找到 s 中所有 p异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

Code:

窗口不合理的情况比较复杂

带注释版本

/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s, p) {
    //哈希(异位词)+滑动窗口(子串)
    //异位词的特点是:
    //1.长度相等
    //2.每个字符的出现次数相等
    let hash = new Map();
    let hash2 = new Map();
    let res = [];
    //初始化hash
    for(let i = 0; i < p.length; i++){
        if(hash.has(p[i])){
            hash.set(p[i], hash.get(p[i]) + 1);
        }else hash.set(p[i], 1);
    }
    //初始化窗口,并开始滑动
    let st = 0;
    for(let end = 0; end < s.length; end++){
        let c = s[end];
        if(hash2.has(c)){
            hash2.set(c, hash2.get(c) + 1);
        }else hash2.set(c, 1);

        //窗口不合法1,没有这个字符,两个指针都跳到这个指针后面
        if(!hash.has(c)){
            while(st != end + 1){//debug1,一开始没有跳转到st = end+1,只是st++,这样是不对的,而且跳转完之后,一定要记得移除hash2中前面的(已经不在滑动窗口中的)字母
                hash2.set(s[st], hash2.get(s[st]) - 1);
                st++;
            }
        }else if(hash2.get(c) > hash.get(c)){
            //窗口不合法2,有这个字符,但是字符数多了,移动st,直到窗口中的c的字符数与hash中的字符数一致
            while(hash2.get(c) != hash.get(c)){
                hash2.set(s[st], hash2.get(s[st]) - 1);
                st++;
            }
        }
        //窗口不合法3,窗口长度超过了
        while(end - st + 1 > p.length){
            hash2.set(s[st], hash2.get(s[st]) - 1);//debug2,滑动了窗口,但是忘记处理hash2了
            st++;
        }
        if(end - st + 1 === p.length){
            res.push(st);
        }
    }
    return res;
};

不带注释版本

/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s, p) {
    let hash = new Map();
    let hash2 = new Map();
    let res = [];
    for(let i = 0; i < p.length; i++){
        if(hash.has(p[i])){
            hash.set(p[i], hash.get(p[i]) + 1);
        }else hash.set(p[i], 1);
    }
    let st = 0;
    for(let end = 0; end < s.length; end++){
        let c = s[end];
        if(hash2.has(c)){
            hash2.set(c, hash2.get(c) + 1);
        }else hash2.set(c, 1);
        if(!hash.has(c)){
            while(st != end + 1){
                hash2.set(s[st], hash2.get(s[st]) - 1);
                st++;
            }
        }else if(hash2.get(c) > hash.get(c)){
            while(hash2.get(c) != hash.get(c)){
                hash2.set(s[st], hash2.get(s[st]) - 1);
                st++;
            }
        }
        while(end - st + 1 > p.length){
            hash2.set(s[st], hash2.get(s[st]) - 1);
            st++;
        }
        if(end - st + 1 === p.length){
            res.push(st);
        }
    }
    return res;
};
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值