前言
双指针是数组类题目高频解法,核心思想:用两个指针代替双重循环,把 O ( n 2 ) O(n^2) O(n2) 复杂度优化至 KaTeX parse error: Can't use function '\(' in math mode at position 1: \̲(̲O(n)\)。
常见两类模式:
- 同向双指针(快慢指针):左右指针一起从左向右移动,典型场景:移动零、去重
- 相向双指针(左右指针):左指针起始左端、右指针起始右端,向中间收拢,典型场景:盛最多水容器、三数之和、接雨水
核心准则:移动指针的策略必须有明确单调性,保证不会漏掉最优解。
1. 283. 移动零(同向快慢双指针)
思路
left 指向待填充非零元素的位置,right 不断遍历数组;遇到非零数就交换左右指针元素,保证非零元素相对顺序不变,所有零被挤到数组末尾。
java
class Solution {
public void moveZeroes(int[] nums) {
int n = nums.length;
// left:下一个非零元素应该存放的位置
int left = 0;
// right:遍历探测指针
int right = 0;
while (right < n) {
// right找到非0元素,和left位置交换
if (nums[right] != 0) {
swap(nums, left, right);
left++; // left位置已存入有效值,右移
}
right++; // 探测指针持续向右
}
}
private void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
// 重点备注:同向双指针,快慢分离;只在探测到非零元素时移动慢指针,零元素会自然留在left左侧以外区域。
// 进阶优化:可以不交换,先把非零值前移,最后末尾统一赋值0,减少交换操作次数。
2. 11. 盛最多水的容器(相向双指针)
思路
左右指针分别在两端,计算当前容器面积;移动高度更小的一侧指针 。
原理:容器高度由短板决定,如果移动长板,宽度缩小、高度上限不变,面积一定不会更大;只有移动短板,才有可能找到更高边界获得更大面积。
java
class Solution {
public int maxArea(int[] height) {
// 左指针起点
int l = 0;
// 右指针起点
int r = height.length - 1;
int max = 0; // 保存最大面积
while (l < r) {
// 容器高度 = 两侧高度最小值;宽度 = r-l
int area = Math.min(height[l], height[r]) * (r - l);
max = Math.max(area, max);
// 核心:移动高度更小的指针
if (height[l] < height[r]) {
l++;
} else {
r--;
}
}
return max;
}
}
// 重点备注:相向双指针经典题,指针移动逻辑是考点 ,切忌随机移动指针。
// 易错坑:不要写成 (r-1) 计算宽度,宽度直接 r-l。
3. 15. 三数之和(排序 + 相向双指针 + 去重)
思路
- 先数组排序,固定第一个元素 first;
- second 为 first+1(左指针),third 数组末尾(右指针);
- 三数和 > 0:右指针左移;和 < 0:左指针右移;和=0记录答案;
- 必须增加去重逻辑,避免重复三元组。
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums); // 排序是双指针使用前提
List<List<Integer>> ans = new ArrayList<>();
// 固定第一个数
for (int first = 0; first < n; first++) {
// 对first去重:和上一个数字相同直接跳过
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
int target = -nums[first]; // 需要 second + third = target
int third = n - 1; // 右指针初始在末尾
// second 左指针,从first下一位开始
for (int second = first + 1; second < n; second++) {
// second去重
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 和大于目标值,右指针持续左移
while (second < third && nums[second] + nums[third] > target) {
third--;
}
// 指针相遇,无需继续
if (second == third) {
break;
}
// 找到合法三元组
if (nums[second] + nums[third] == target) {
List<Integer> list = new ArrayList<>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
}
}
// 重点备注:三数之和 = 一层循环 + 两数之和(相向双指针)。
// 重中之重:三层去重逻辑,first、second重复元素都需要跳过,否则出现重复答案。
// 优化写法:内层可以不用for循环second,改用while实现双指针,可读性更好。
4. 42. 接雨水(前置:预处理数组解法,拓展双指针优化)
思路(预处理左右最大高度数组版本)
位置 i 能接住雨水 = min(左侧最高柱子高度, 右侧最高柱子高度) - 当前柱子高度
先预处理 leftMax 数组:每个位置左边最高高度;rightMax数组:每个位置右边最高高度。
java
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) return 0;
int[] leftMax = new int[n];
leftMax[0] = height[0];
// 正向遍历,求每个位置左侧最大高度
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
int[] rightMax = new int[n];
rightMax[n - 1] = height[n - 1];
// 反向遍历,求每个位置右侧最大高度
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
int ans = 0;
// 逐个位置累加雨水
for (int i = 0; i < n; i++) {
// 左右挡板短板 - 当前高度
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
// 重点备注:预处理方法直观,便于理解原理;缺点占用额外数组。
// 进阶相向双指针思路:维护leftMax、rightMax两个变量,左右指针向中间移动;哪边挡板更小,就计算当前一侧雨水,空间复杂度压缩到O(1)。
双指针题型总结
同向双指针(快慢指针)
适用场景:数组元素就地移动、过滤元素、滑动窗口
代表题目:移动零、删除有序数组重复项
相向双指针(左右指针)
适用场景:有序数组查找两数/三数之和、两侧边界约束问题
代表题目:盛最多水容器、三数之和、接雨水(优化版)
通用做题思考步骤
- 判断数组是否需要排序;
- 确定指针初始位置;
- 明确什么时候移动哪个指针(核心难点,要有单调性证明);
- 重复类题目额外增加去重判断;
- 边界处理:指针越界、两指针相遇终止条件。