跳到主内容

滑动窗口技巧详解:双指针算法的5个高频场景与代码模板 ​

「找出最长的无重复字符子串」「长度为 k 的子数组最大和」「两数之和 II」——这些高频算法题背后是同一个套路:暴力解法都是双重循环 O(n²),而窗口与指针的移动把它压到 O(n)。滑动窗口本质上是双指针的一个特例:两个指针同向移动,中间夹出一个动态区间,区间内的信息增量维护、不重复计算。本文按五类高频场景拆解这个套路的适用条件与代码模板。

先判断能不能用:窗口题的三个特征 ​

不是所有区间问题都能滑窗,动手前先看三个特征:连续性——目标对象必须是连续的子数组/子串,跳着选元素的不行;单调性——窗口扩大时指标单调变化(和不减、计数不减),缩小时反向单调,这样才能用两个指针夹出答案;可增量维护——窗口内的统计量(和、计数、哈希表)能在指针移动时 O(1) 更新。三个条件都满足,就可以放心套模板。

场景一:定长窗口——固定 k 求极值 ​

最基础的形态:窗口大小固定为 k,求窗口内的最大和或均值。套路是先建立第一个窗口,之后每次「进一个、出一个」:

javascript
// 长度为 k 的子数组最大和:O(n)
function maxSumFixed(nums, k) {
  let sum = 0;
  for (let i = 0; i < k; i++) sum += nums[i];      // 建立首个窗口
  let max = sum;
  for (let i = k; i < nums.length; i++) {
    sum += nums[i] - nums[i - k];                  // 进 i 出 i-k,增量更新
    max = Math.max(max, sum);
  }
  return max;
}

场景二:变长窗口求最长——「不重复字符子串」类 ​

窗口长度不固定,目标是找到满足条件的最长窗口。模板是右指针一直扩张,条件被破坏时才收缩左指针:

javascript
// 无重复字符的最长子串:O(n)
function lengthOfLongestSubstring(s) {
  const pos = new Map();          // 字符 → 最近出现的下标
  let left = 0, best = 0;
  for (let right = 0; right < s.length; right++) {
    const ch = s[right];
    if (pos.has(ch) && pos.get(ch) >= left) {
      left = pos.get(ch) + 1;     // 重复字符在窗口内:左指针直接跳过它
    }
    pos.set(ch, right);
    best = Math.max(best, right - left + 1);
  }
  return best;
}

滑动窗口:双指针维护连续区间abcadeafgleftrightright 扩张扩张:right 向右加入元素窗口不满足条件时持续延伸收缩:left 向右移除元素满足条件后压缩到最优窗口

场景三:变长窗口求最短——「最短覆盖子串」类 ​

与场景二方向相反:先扩张到满足条件,再尽量收缩求最短。经典题是「长度最小的子数组」(和 ≥ target):

javascript
// 和 ≥ target 的最短子数组:O(n)
function minSubArrayLen(target, nums) {
  let left = 0, sum = 0, best = Infinity;
  for (let right = 0; right < nums.length; right++) {
    sum += nums[right];                       // 扩张
    while (sum >= target) {                   // 满足条件时尽量收缩
      best = Math.min(best, right - left + 1);
      sum -= nums[left++];
    }
  }
  return best === Infinity ? 0 : best;
}

记忆口诀:求最长,破坏才缩;求最短,满足就缩。两类模板的骨架完全一致,区别只在收缩时机与答案更新位置。

场景四与场景五:快慢双指针与相向双指针 ​

快慢双指针处理原地去重、移除元素这类原地修改:快指针探路,慢指针维护「已处理区」的尾部,如有序数组原地去重就是快指针遇新元素写入慢指针位置。相向双指针则一个从头一个从尾向中间收拢,利用序列的单调性跳过无效组合:「两数之和 II」中,和大了左移右指针、和小了右移左指针,每步都能确定性地淘汰一个候选,把 O(n²) 降为 O(n)。

复杂度与易错点 ​

场景指针移动典型题目复杂度
定长窗口同向,同步子数组最大和 IO(n)
变长求最长同向,破坏才缩无重复字符子串O(n)
变长求最短同向,满足就缩长度最小子数组O(n)
快慢指针同向,异速原地去重O(n)
相向指针从两端向中间两数之和 IIO(n)

两个高频易错点:其一,左指针收缩必须用 while 而不是 if——条件可能要连续收缩多次才能恢复;其二,求最短/求最长的初始值分别用 Infinity 与 0,别用 -1 混着判空。滑动窗口的每个模板都值得手写到肌肉记忆,因为面试现场留给你的思考时间,往往只够识别「这是哪一类窗口」。

实践清单 ​

  1. 动手前先验证连续性、单调性、可增量维护三特征。
  2. 记牢口诀:求最长破坏才缩,求最短满足就缩。
  3. 收缩循环用 while,统计量更新与指针移动严格配对。
  4. 用 [1,1,1]、全相同数组、空数组三个用例检验边界。

滑动窗口是「用空间换时间的重计算」的典型反例——它证明的是:只要区间指标能增量维护,O(n²) 的枚举就永远有被压成 O(n) 的机会。识别这种机会的能力,正是算法训练真正的产出。

免责声明

本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。