一、🔍 本质定义
双指针是一种用两个指针遍历数据结构的技巧,常用于:
| 场景 | 描述 |
|---|---|
| 对撞指针 | 从两端向中间收缩(例如:盛水容器、两数之和) |
| 快慢指针 | 用于检测环、找中点(例如:链表是否有环) |
| 滑动窗口 | 一种特殊的双指针,维护一个动态区间(例如:最小覆盖子串、无重复子串) |
二、🧠 常见题型总结
| 类型 | 代表题 | 技巧 |
|---|---|---|
| ✅ 对撞型 | LeetCode 11. 盛最多水的容器 | 左右收缩,跳过不可能的解 |
| ✅ 快慢型 | LeetCode 141. 环形链表 | 快走2步、慢走1步找循环 |
| ✅ 滑动窗口 | LeetCode 76、3、567、438 | 左右维护一个区间窗口,配合 Map 或 Set |
三、🛠 通用模板(滑动窗口版本最通用)
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++;
}
}
📌 常配合 need 和 window 两个哈希表,比如 window[c] >= need[c] 来判断条件是否满足。
四、🎯 解题通用思维流程
-
读题识别能不能用双指针:
- 有没有“连续子区间”?
- 有没有“两个端点”或“扫描+比较”过程?
-
定义指针含义:
- 指向字符?
- 指向窗口的边界?
-
明确窗口收缩/扩展的时机
- 满足条件时更新答案 or 缩小范围
-
找出最值、个数还是索引?
五、💡 优化技巧合集
| 技巧 | 场景 | 示例 |
|---|---|---|
| 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 问题
滑动窗口时,常见
left和right指针处理区间包含/不包含的问题。
示例:
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)(更快) |
八、📚 一句话总结
双指针是一种以空间换时间的技巧,适合处理有序结构、子区间判断、滑动窗口等问题,常用于优化暴力枚举、降低复杂度。