算法技巧-滑动窗口(sliding window)

4,540 阅读4分钟

滑动窗口(sliding window)是一种常见的算法技巧,通常用于解决字符串、数组等数据结构相关的问题。

滑动窗口算法的基本思想是维护一个滑动窗口,该窗口通常是一个固定大小的子序列,并且该子序列通常是连续的。然后,通过滑动窗口的方式,不断地更新窗口的位置和大小,以便解决问题。

image.png

基本思路

具体而言,滑动窗口算法的常见步骤如下: 滑动窗口算法的思路:

  1. 在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0 ,把索引左闭右开区间  [left, right)  称为一个窗口。
  2. 不断地增加 right 指针扩大窗口  [left, right)  ,直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
  3. 此时停止增加 right ,转而不断增加 left 指针缩小窗口  [left, right)  ,直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left ,都要更新一轮结果。
  4. 重复第2和第3步,直到 right 到达字符串 S 的尽头。

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

开始套模板之前,要思考以下四个问题:

  1. 当移动right扩大窗口,即加入字符时,应该更新哪些数据?
  2. 什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
  3. 当移动left缩小窗口,即移出字符时,应该更新哪些数据?
  4. 我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?

常见应用

最小覆盖子串(Minimum Window Substring)

leetcode 76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:
输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

public class Solution76 {
    public String minWindow(String s, String t) {
        char[] originChs = s.toCharArray(), destChs = t.toCharArray();
        int originLen = originChs.length, destLen = t.length();
        // 将char存入到数组作为hash
        int[] hash = new int[128];
        for (int i = 0; i < destLen; i++) {
            hash[destChs[i]]--;
        }
        String result = "";
        // 左闭右开,count是当前待符合条件长度
        for (int left = 0, right = 0, count = 0; right < originLen; right++) {
            hash[originChs[right]]++;
            if (hash[originChs[right]] <= 0) {
                count++;
            }
            // left缩小窗口条件
            while (count == destLen && hash[originChs[left]] > 0) {
                hash[originChs[left++]]--;
            }
            if (count == destLen) {
                //符合最小覆盖子串条件
                if (result.equals("") || result.length() > right + 1 - left) {
                    result = s.substring(left, right + 1);
                }
            }
        }
        return result;
    }

    @Test
    public void testCase01() {
        String s = "ADOBECODEBANC", t = "ABC";
        String result = minWindow(s, t);
        Assert.assertEquals(result, "BANC");
    }

    @Test
    public void testCase02() {
        String s = "a", t = "a";
        String result = minWindow(s, t);
        Assert.assertEquals(result, "a");
    }

    @Test
    public void testCase03() {
        String s = "a", t = "aa";
        String result = minWindow(s, t);
        Assert.assertEquals(result, "");
    }
}

长度最小的子数组

leetcode 209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:

输入: target = 7, nums = [2,3,1,2,4,3] 输出: 2 解释: 子数组 [4,3] 是该条件下的长度最小的子数组。

public class Solution209 {
    public int minSubArrayLen(int target, int[] nums) {
        int len = nums.length, ret = 0;
        for (int left = 0, right = 0, count = 0; right < len; right++) {
            count += nums[right];
            while (count >= target && count - nums[left] >= target) {
                count -= nums[left++];
            }
            if (count >= target) {
                if (ret ==0 || right - left + 1 < ret) {
                    ret = right + 1 - left;
                }
            }

        }
        return ret;
    }

    @Test
    public void testCase01() {
        int result = minSubArrayLen(7, new int[]{2, 3, 1, 2, 4, 3});
        Assert.assertEquals(result, 2);
    }

    @Test
    public void testCase02() {
        int result = minSubArrayLen(4, new int[]{1, 4, 4});
        Assert.assertEquals(result, 1);
    }

    @Test
    public void testCase03() {
        int result = minSubArrayLen(11, new int[]{1, 1, 1, 1, 1, 1, 1, 1});
        Assert.assertEquals(result, 0);
    }
}

其他算法题

leetcode 438. 找到字符串中所有字母异位词
leetcode 3. 无重复字符的最长子串
leetcode 674. 最长连续递增序列

开源软件应用

Sentinel滑动窗口

Sentinel 是面向分布式服务框架的轻量级流量控制框架,主要以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度来维护系统的稳定性。
滑动窗口核心实现类LeapArray
//具体实现代码和应用场景待分析