JS:滑动窗口算法 (上集)

在这里插入图片描述

建议先读文章,本文解决一类最难掌握的双指针技巧:滑动窗口技巧。

难度在于各种细节问题,比如:如何向窗口中添加新元素,如何缩小窗口,在窗口滑动的哪个阶段更新结果。

技术:

  1. 用到的数据结构——哈希表(字典)
  2. 直接使用对象来代替map,1. 获取键的长度 , 2. 不需要get,set方法。
  3. 字符串遍历
  4. window[c] = (window[c] || 0) + 1; 如果键不存在时,赋初始值。不需要先判断

代码模板:

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    
    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        //printf("window: [%d, %d)\n", left, right);
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
        	// 在这里更新最小覆盖子串
        	...
        	
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
    return 所需字符串
}

在这里插入图片描述

76. 最小覆盖子串 (困难)

在这里插入图片描述

这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解,也就是最短的覆盖子串。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动,这就是「滑动窗口」这个名字的来历。

needs 和 window 相当于计数器,分别记录 T 中字符出现次数和「窗口」中的相应字符的出现次数。

进行窗口内数据的一系列更新:window的字符数量增加,统计覆盖的字符数

/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
var minWindow = function(s, t) {
    let need = new Map(), window = new Map();
    for (let c of t){
        need.set(c, (need.get(c) || 0) + 1); //代码功底
    }
    let left = right = count = start = 0;
    let minlen = Number.MAX_SAFE_INTEGER;
    while (right < s.length) {
        let c = s[right];
        right++;
        // 当前字符在t字符串中,才更新窗口的统计
        if(need.has(c)) {
            window.set(c, (window.get(c) || 0) + 1);
            // 等于, 才说明某字符完成覆盖
            if (window.get(c) == need.get(c)) {
                count++;
            }
        }
        // 判断左侧窗口是否要收缩
        while (count == need.size) {
            let temp = right - left;
            if (temp < minlen) {
                start = left;
                minlen = temp;
            }
            let c = s[left];
            left++;
            if(need.has(c)) {
            	//再减就不够数覆盖某字符
                if (window.get(c) == need.get(c)) {
                    count--;
                }
                window.set(c, window.get(c) - 1);
            }
        }
    }
    return minlen == Number.MAX_SAFE_INTEGER ? "" : s.substr(start, minlen);
};

可参考

567. 字符串的排列 (中等)

在这里插入图片描述

注意:输入的 s1 是可以包含重复字符的。排列之一:包含s1中所有字符,不能多余且不包含其他字符。即:窗口大小等于s1的长度,窗口大小固定也能运用上面的框架,先移入后移出。

技巧:要覆盖s1字符时,因为窗口限制了大小,不可能有多余的字符,

区别:如果所有字符都覆盖了,则结束。而上一题则是开始缩圈。

/**
 * @param {string} s1
 * @param {string} s2
 * @return {boolean}
 */
var checkInclusion = function(s1, s2) {
    let need = {}, window = {};
    for (let c of s1) {
        need[c] = (need[c] || 0) + 1;
    }
    let left = right = count = 0;
    let maxlen = s1.length, target = Object.keys(need).length;
    while (right < s2.length) {
        let c = s2[right]
        right++
        // 处理移入的字符
        if(need[c]) {
            window[c] = (window[c] || 0) + 1;
            if(window[c] == need[c]) count++;
        }
        // 判断左侧窗口是否要收缩
        while (right-left == maxlen) {
            // 判断结果
            if (count == target) return true;
            let c = s2[left]
            left++
            // 处理移入的字符
            if(need[c]) {                
                if(window[c] == need[c]) count--;
                window[c]--;
            }
        }
    }
    return false;
};

个人觉得:有了这个框架,优点:更容易记住,不需要管别人怎么写了。缺点:一开始花时间。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值