算法 —— 双指针

131 阅读4分钟

一、🔍 本质定义

双指针是一种用两个指针遍历数据结构的技巧,常用于:

场景描述
对撞指针从两端向中间收缩(例如:盛水容器、两数之和)
快慢指针用于检测环、找中点(例如:链表是否有环)
滑动窗口一种特殊的双指针,维护一个动态区间(例如:最小覆盖子串、无重复子串)

二、🧠 常见题型总结

类型代表题技巧
✅ 对撞型LeetCode 11. 盛最多水的容器左右收缩,跳过不可能的解
✅ 快慢型LeetCode 141. 环形链表快走2步、慢走1步找循环
✅ 滑动窗口LeetCode 76、3、567、438左右维护一个区间窗口,配合 MapSet

三、🛠 通用模板(滑动窗口版本最通用)

js
复制编辑
let left = 0;
for (let right = 0; right < s.length; right++) {
  // 1. 扩大窗口
  window[s[right]]++;

  // 2. 满足某种条件时,开始收缩窗口
  while (满足某种条件) {
    // 更新最优解
    result = Math.min(result, right - left + 1);

    // 收缩窗口
    window[s[left]]--;
    left++;
  }
}

📌 常配合 needwindow 两个哈希表,比如 window[c] >= need[c] 来判断条件是否满足。


四、🎯 解题通用思维流程

  1. 读题识别能不能用双指针:

    • 有没有“连续子区间”?
    • 有没有“两个端点”或“扫描+比较”过程?
  2. 定义指针含义:

    • 指向字符?
    • 指向窗口的边界?
  3. 明确窗口收缩/扩展的时机

    • 满足条件时更新答案 or 缩小范围
  4. 找出最值、个数还是索引?


五、💡 优化技巧合集

技巧场景示例
1. 跳过不必要的字符只处理 t 中的字符76题中 if (!need[s[r]]) continue
2. 提前终止满足条件立刻更新if (right - left + 1 > k)
3. 使用字符频次数组替代 Map字符是 ASCII 的时候const need = Array(128).fill(0)
4. 空间优化滑窗不需要记录全部字符Set + 计数器代替 Map

六、🌟 高频面试题总结

题目难度涉及技巧
11. 盛最多水的容器⭐⭐对撞双指针
3. 无重复字符的最长子串⭐⭐⭐滑动窗口 + Set
76. 最小覆盖子串⭐⭐⭐⭐滑动窗口 + Map + 频次判断
438. 找所有异位词⭐⭐⭐滑动窗口 + Map + 固定窗口大小
283. 移动零快慢双指针原地交换
42. 接雨水⭐⭐⭐⭐对撞双指针 + 高度比较

七、 🧨 双指针常见坑点汇总


❗️1. 窗口边界 off-by-one 问题

滑动窗口时,常见 leftright 指针处理区间包含/不包含的问题。

示例:

js
复制编辑
while (right < s.length) {
  // right 是包含字符的索引([left, right])
  ...
}
  • 固定窗口大小 时要判断 (right - left + 1)
  • 变动窗口 时注意 while (valid === need.size) 这样的条件

❗️2. 计数频次更新顺序错误

特别是在使用 Map数组 做字符频次时:

  • 更新顺序错了,会导致窗口判断提前/延迟
  • 常见错误:valid++ 的时机写错(应只在某字符频次刚好匹配时更新)

❗️3. 包含 vs 等于的逻辑混淆

  • 题目要求可能是“包含所有字符”(如 LeetCode 76)
  • 你可能写成“刚好相等”或者“只出现一次”

错误示例:

js
复制编辑
if (window[c] === 1) valid++; // ❌ 不适用于重复字符情况

❗️4. 字符串中有重复字符处理不当

  • 比如 t = "AABC" 这种,不能只用 Set 判断
  • 必须用 Map 或数组记录字符频率

❗️5. 没有考虑 s 比 t 短的情况

  • 滑动窗口类题目必须有 base case:
js
复制编辑
if (s.length < t.length) return "";

❗️6. 扩展/收缩窗口的条件写错

  • 滑窗题的本质:右边扩展直到“条件满足”,然后左边收缩以“缩到最小”

⏱ 常见时间/空间复杂度总结

场景时间复杂度空间复杂度举例
✅ 对撞指针O(n)O(1)盛水容器、接雨水(非单调栈写法)
✅ 快慢指针O(n)O(1)链表环、删除重复节点
✅ 滑动窗口(不含 Map)O(n)O(1)~O(n)最长无重复子串
✅ 滑动窗口(带 Map/频次)O(n)O(k) (k为字符种类)最小覆盖子串、异位词查找
❗️错误写法O(n²)-频次判断没优化,导致每次都重新计算

🎯 优化技巧对复杂度的影响:

技巧结果
使用 Map 替代频繁遍历O(n²) ➜ O(n)
提前剪枝减少不必要的 while 循环
用数组代替 Map(字符是 ASCII)空间从 O(k) ➜ O(128)(更快)

八、📚 一句话总结

双指针是一种以空间换时间的技巧,适合处理有序结构、子区间判断、滑动窗口等问题,常用于优化暴力枚举、降低复杂度。