滑动窗口模板

维护一个窗口 [left, right],right 不断向右扩展,满足条件时 left 向右收缩,窗口内的元素始终满足某个条件。

适用场景: 子数组 / 子串问题,求最长 / 最短 / 数量


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int slidingWindow(vector<int>& nums) {
    int left = 0, ans = 0;
    // 窗口状态变量(计数、求和等)

    for (int right = 0; right < nums.size(); right++) {
        // 1. 右端点进入窗口,更新窗口状态

        while (窗口不满足条件) {  // 或 if,看题目
            // 2. 左端点移出窗口,更新窗口状态
            left++;
        }

        // 3. 此时窗口是合法的,更新答案
        ans = max(ans, right - left + 1);
    }
    return ans;
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int lengthOfLongestSubstring(string s) {
    unordered_map<char, int> window;
    int left = 0, ans = 0;

    for (int right = 0; right < s.size(); right++) {
        char c = s[right];
        window[c]++;

        while (window[c] > 1) {  // 出现重复,收缩
            char d = s[left];
            window[d]--;
            left++;
        }

        ans = max(ans, right - left + 1);
    }
    return ans;
}

找 s 中包含 t 所有字符的最短子串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
string minWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, valid = 0;
    int start = 0, minLen = INT_MAX;

    for (int right = 0; right < s.size(); right++) {
        char c = s[right];
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c]) valid++;
        }

        while (valid == need.size()) {  // 满足条件,收缩找最小
            if (right - left + 1 < minLen) {
                minLen = right - left + 1;
                start = left;
            }

            char d = s[left];
            if (need.count(d)) {
                if (window[d] == need[d]) valid--;
                window[d]--;
            }
            left++;
        }
    }
    return minLen == INT_MAX ? "" : s.substr(start, minLen);
}

注意:有负数时不能用滑动窗口,因为窗口不单调,要用前缀和+哈希。


  • 数组元素都是正数(窗口扩展和增大,收缩和减小)
  • 求子数组的最长 / 最短 / 数量
  • 条件具有单调性:窗口变大条件从满足变不满足(或反过来)
  • 有负数(窗口增大和不一定增大)
  • 求子序列(不连续)
  • 条件不单调

窗口大小固定为 k,滑动遍历:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 大小为 k 的子数组最大和
int maxSum(vector<int>& nums, int k) {
    int sum = 0;
    for (int i = 0; i < k; i++) sum += nums[i];  // 第一个窗口

    int ans = sum;
    for (int i = k; i < nums.size(); i++) {
        sum += nums[i] - nums[i - k];  // 滑一格:加新的,减出去的
        ans = max(ans, sum);
    }
    return ans;
}

类型代表题思路
最长无重复LeetCode 3哈希表计数,重复就收缩
最小覆盖子串LeetCode 76need + window + valid
字母异位词LeetCode 438固定窗口,比较计数
最长重复字符替换LeetCode 424窗口内最多字符数 + k
水果成篮LeetCode 904最多两种水果,最长子数组
和为 s 的最短子数组LeetCode 209正整数,收缩求最短
乘积小于 K 的子数组LeetCode 713正数,收缩计数

  1. 初始化 left = 0,答案变量
  2. 遍历 right 从 0 到 n-1
  3. 右端点加入窗口,更新状态
  4. 窗口不合法时,while 循环收缩左端点
  5. 窗口合法时,更新答案

口诀:右扩左缩,边扩边更,缩完算答

相关内容