【力扣热题100学习笔记】 - 双指针
📚 适合人群 :编程基础较弱,想要系统学习双指针算法的同学
🎯 学习目标:掌握双指针的核心思想,能够独立解决类似问题
一、什么是双指针?
双指针 是一种算法技巧,使用两个变量(指针)在数组或链表中移动,通过协作来解决问题。
核心思想:
- 用两个指针指向不同的位置
- 根据题目要求,两个指针以某种方式移动
- 通常能将时间复杂度从 O(n²) 优化到 O(n)
二、双指针的两种经典模式
模式 1:快慢指针(同向移动)
特点:
- 两个指针都从头开始
- 快指针走得快,慢指针走得慢
- 用于数组内元素重排问题
典型题目:移动零
模式 2:左右指针(相向移动)
特点:
- 一个指针在开头,一个在末尾
- 两个指针相向而行
- 用于查找满足条件的两个元素
典型题目:盛最多水的容器、三数之和
三、题目详解
📝 题目 1:移动零(快慢指针)
题目描述 :
给定一个数组 nums,将所有 0 移动到数组末尾,保持非零元素的相对顺序。
示例:
输入:[0,1,0,3,12]
输出:[1,3,12,0,0]
解题思路
步骤分解:
-
定义两个指针:
slow:慢指针,记录下一个非 0 元素应该放的位置fast:快指针,遍历整个数组
-
快指针的作用:
- 从头到尾遍历数组
- 检查每个元素是不是 0
-
慢指针的作用:
- 只有遇到非 0 元素时才移动
- 记录非 0 元素应该放的位置
-
移动规则:
fast每次都向前走- 如果
nums[fast] != 0,把它放到slow位置,然后slow++ - 如果
nums[fast] == 0,跳过
代码实现
java
class Solution {
public void moveZeroes(int[] nums) {
int slow = 0; // 慢指针:下一个非 0 元素的位置
// 快指针遍历数组
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != 0) {
// 把非 0 元素放到 slow 位置
nums[slow] = nums[fast];
slow++; // slow 向前移动
}
}
// 把后面的位置都填 0
for (int i = slow; i < nums.length; i++) {
nums[i] = 0;
}
}
}
复杂度分析
- 时间复杂度:O(n) - 遍历两遍数组
- 空间复杂度:O(1) - 只用了常数个变量
关键点总结
✅ 为什么叫快慢指针?
fast每次都走 → 走得快slow只有遇到非 0 才走 → 走得慢
✅ 为什么要用两个指针?
- 一个负责遍历(找非 0 元素)
- 一个负责记录位置(放非 0 元素)
✅ 为什么能保持相对顺序?
- 因为是从左到右遍历,非 0 元素按原来的顺序依次往前放
📝 题目 2:盛最多水的容器(左右指针)
题目描述 :
给定 n 条垂线,找出两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
示例:

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解题思路
面积公式:
面积 = 宽度 × 高度
宽度 = right - left
高度 = min(height[left], height[right]) ← 短板效应
步骤分解:
-
定义两个指针:
left:最左边(下标 0)right:最右边(下标 n-1)
-
为什么要从两端开始?
- 这时候宽度最大
- 有机会找到更大的面积
-
移动规则(贪心思想):
- 如果
height[left] < height[right]→ 移动left - 如果
height[left] > height[right]→ 移动right - 谁短移动谁!
- 如果
-
为什么移动短的?
- 面积由短板决定
- 移动长的,高度最多还是这么高(甚至更矮),宽度还变小了
- 移动短的,可能遇到更高的板子,面积可能变大
代码实现
java
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int maxArea = 0;
while (left < right) {
// 计算当前面积
int currentArea = Math.min(height[left], height[right]) * (right - left);
// 更新最大面积
maxArea = Math.max(maxArea, currentArea);
// 移动短板(谁短移谁)
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
}
图解过程
以 [1,8,6,2,5,4,8,3,7] 为例:
初始:left=0, right=8
| |
| |
| |
| |
| |
| |
| |
____|____________________|____
0 8
高度 = min(1, 7) = 1
宽度 = 8 - 0 = 8
面积 = 1 × 8 = 8
left 短,移动 left → left=1
| |
| |
| |
| |
| |
| |
| |
____|____________________|____
1 8
高度 = min(8, 7) = 7
宽度 = 8 - 1 = 7
面积 = 7 × 7 = 49 ✓ 最大面积!
继续移动...最终找到最大面积 49
复杂度分析
- 时间复杂度:O(n) - 只需要遍历一遍
- 空间复杂度:O(1) - 只用了两个指针
关键点总结
✅ 为什么从两端开始?
- 宽度最大,有机会找到更大的面积
✅ 为什么移动短板?
- 移动长板:面积肯定变小(宽度变小,高度不变或变小)
- 移动短板:面积可能变大(可能遇到更高的板子)
✅ 这是贪心算法吗?
- 是的!每一步都做出当前看起来最优的选择(移动短板)
📝 题目 3:三数之和(左右指针 + 排序)
题目描述 :
找出数组中所有和为 0 的三元组,答案中不可以包含重复的三元组。
示例:
输入:[-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解题思路
问题转化:
- 固定一个数
nums[i] - 问题变成:在剩下的数里找两个数,让它们的和等于
-nums[i] - 这就变成了两数之和问题!
步骤分解:
-
排序(关键!):
- 排序后相同的数会挨在一起,方便去重
- 可以用左右指针
-
固定一个数:
- 遍历数组,固定
nums[i]作为第一个数
- 遍历数组,固定
-
双指针找另外两个数:
left = i + 1(从 i 的下一个开始)right = n - 1(从最后一个开始)- 计算
sum = nums[left] + nums[right] - 如果
sum == -nums[i],找到一组! - 如果
sum < -nums[i],left++(和太小) - 如果
sum > -nums[i],right--(和太大)
-
去重(关键!):
- 如果
nums[i] == nums[i-1],跳过(避免重复的三元组) - 找到一组后,跳过重复的
left和right
- 如果
代码实现
java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
// 排序(关键!)
Arrays.sort(nums);
// 遍历每个数,固定它作为第一个数
for (int i = 0; i < nums.length; i++) {
// 剪枝:如果当前数大于 0,后面的数都比它大,三数之和不可能为 0
if (nums[i] > 0) break;
// 去重:如果和前一个数相同,跳过
if (i > 0 && nums[i] == nums[i-1]) continue;
// 双指针找另外两个数
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
// 找到一组
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重:跳过重复的 left 和 right
while (left < right && nums[left] == nums[left+1]) left++;
while (left < right && nums[right] == nums[right-1]) right--;
// 移动指针
left++;
right--;
} else if (sum < 0) {
// 和太小,左指针右移
left++;
} else {
// 和太大,右指针左移
right--;
}
}
}
return result;
}
}
图解过程
https://leetcode.cn/problems/3sum/solutions/12307/hua-jie-suan-fa-15-san-shu-zhi-he-by-guanpengchn
以 [-1,0,1,2,-1,-4] 为例:
1. 排序后:[-4, -1, -1, 0, 1, 2]
2. i=0,固定 -4:
left=1(-1), right=5(2)
sum = -4 + (-1) + 2 = -3 < 0 → left++
... 找不到和为 0 的组合
3. i=1,固定 -1:
left=2(-1), right=5(2)
sum = -1 + (-1) + 2 = 0 ✓
找到 [-1, -1, 2]
继续找:
left=3(0), right=4(1)
sum = -1 + 0 + 1 = 0 ✓
找到 [-1, 0, 1]
4. i=2,nums[2]=-1,但 nums[2]==nums[1],跳过!(去重)
5. i=3,固定 0:
left=4(1), right=5(2)
sum = 0 + 1 + 2 = 3 > 0 → right--
... 找不到
最终结果:[[-1,-1,2], [-1,0,1]] ✓
复杂度分析
- 时间复杂度:O(n²) - 排序 O(n log n) + 双指针 O(n²)
- 空间复杂度:O(1) - 除了结果,只用了常数个变量
关键点总结
✅ 为什么要排序?
- 方便去重(相同的数挨在一起)
- 可以用左右指针(有序数组才能判断移动方向)
✅ 为什么要去重?
- 题目要求答案中不可以包含重复的三元组
- 比如
[-1, 0, 1]和[0, 1, -1]算同一个
✅ 怎么去重?
- 固定数去重:如果
nums[i] == nums[i-1],跳过 - 双指针去重:找到一组后,跳过重复的
left和right
四、三种模式对比
| 模式 | 指针位置 | 移动方向 | 典型题目 |
|---|---|---|---|
| 快慢指针 | 都在开头 | 同向移动 | 移动零 |
| 左右指针 | 两端 | 相向移动 | 盛最多水的容器 |
| 固定 + 双指针 | 一个固定,两个在剩余部分两端 | 相向移动 | 三数之和 |
五、解题模板
快慢指针模板
java
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (满足条件) {
// 处理 fast 指向的元素
nums[slow] = nums[fast];
slow++;
}
}
// 后续处理(如填 0)
左右指针模板
java
int left = 0;
int right = nums.length - 1;
while (left < right) {
int result = 计算结果;
if (result == 目标值) {
// 找到答案
} else if (result < 目标值) {
left++; // 太小,左指针右移
} else {
right--; // 太大,右指针左移
}
}
六、复习建议
第一遍:理解思路
- 看懂每道题的图解过程
- 理解为什么要这样移动指针
- 不要死记硬背代码
第二遍:独立 coding
- 遮住答案,自己写代码
- 写不出来就看提示,不要直接看答案
- 写完对比标准答案,找出差距
第三遍:总结规律
- 三道题有什么共同点?
- 双指针的核心思想是什么?
- 什么情况下用快慢指针?什么情况下用左右指针?
后续练习
推荐题目:
- 两数之和 II(输入有序数组)
- 接雨水
- 最长回文子串
- 删除排序数组中的重复项
) {
int result = 计算结果;
if (result == 目标值) {
// 找到答案
} else if (result < 目标值) {
left++; // 太小,左指针右移
} else {
right--; // 太大,右指针左移
}
}
---
## 六、复习建议
### 第一遍:理解思路
1. 看懂每道题的图解过程
2. 理解为什么要这样移动指针
3. 不要死记硬背代码
### 第二遍:独立 coding
1. 遮住答案,自己写代码
2. 写不出来就看提示,不要直接看答案
3. 写完对比标准答案,找出差距
### 第三遍:总结规律
1. 三道题有什么共同点?
2. 双指针的核心思想是什么?
3. 什么情况下用快慢指针?什么情况下用左右指针?
### 后续练习
推荐题目:
- 两数之和 II(输入有序数组)
- 接雨水
- 最长回文子串
- 删除排序数组中的重复项
---
##