1. 移动零(Move Zeroes)
题目描述
给定一个整数数组 nums,将所有 0 移动到数组末尾,同时保持非零元素的相对顺序。要求原地修改,不使用额外空间。
解题思路
使用快慢双指针:
fast指针遍历整个数组;slow指针记录下一个非零元素应放置的位置;- 当
nums[fast] !== 0时,将其交换到slow位置(若fast !== slow可避免无意义交换); - 最终
slow之后的位置自然为0。
💡 关键洞察:非零元素“前移”的过程,本质上是将有效元素紧凑排列。
代码实现
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var moveZeroes = function(nums) {
let slow = 0;
for (let fast = 0; fast < nums.length; fast++) {
if (nums[fast] !== 0) {
if (fast !== slow) {
[nums[slow], nums[fast]] = [nums[fast], nums[slow]];
}
slow++;
}
}
};
复杂度分析
- 时间复杂度:O(n),仅一次遍历;
- 空间复杂度:O(1),原地操作。
2. 盛最多水的容器(Container With Most Water)
题目描述
给定一个非负整数数组 height,每个元素代表坐标系中一个柱子的高度。选择两个柱子,使其与 x 轴围成的容器盛水最多,返回最大面积。
解题思路
采用对撞双指针 + 贪心策略:
- 初始时
left = 0,right = n - 1; - 面积 =
(right - left) × min(height[left], height[right]); - 每次移动较矮的一侧:因为面积受限于矮边,移动高边只会让宽度减小而高度不变或更小,无法获得更大面积。
✅ 为什么贪心成立?
若height[left] < height[right],则left与任意k ∈ (left, right)组合的面积都不可能超过当前left-right组合(宽度更小,高度 ≤height[left])。因此可安全舍弃left。
代码实现
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
let max = 0;
let left = 0;
let right = height.length - 1;
while (left < right) {
const width = right - left;
const minHeight = Math.min(height[left], height[right]);
max = Math.max(max, width * minHeight);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return max;
};
复杂度分析
- 时间复杂度:O(n),双指针相遇即止;
- 空间复杂度:O(1)。
3. 三数之和(3Sum)
题目描述
给定整数数组 nums,找出所有不重复的三元组 (a, b, c),使得 a + b + c = 0。
解题思路
- 排序:便于去重和使用双指针;
- 固定第一个数
nums[i],问题转化为“在[i+1, n-1]中找两数之和为-nums[i]”; - 双指针查找:
left = i+1,right = n-1,根据和的大小调整指针; - 跳过重复值:避免结果重复(
nums[i] == nums[i-1]、nums[left] == nums[left+1]等)。
🔍 边界处理:若
nums[0] > 0,则所有数为正,不可能和为 0,直接返回空。
代码实现
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
const ans = [];
nums.sort((a, b) => a - b);
const n = nums.length;
if (n < 3 || nums[0] > 0) return ans;
for (let i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue; // 去重
let left = i + 1;
let right = n - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
ans.push([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--;
}
}
}
return ans;
};
复杂度分析
- 时间复杂度:O(n²),排序 O(n log n) + 外层循环 × 双指针;
- 空间复杂度:O(1)(不计输出空间)。
4. 接雨水(Trapping Rain Water)
题目描述
给定 n 个非负整数表示柱状图高度,计算按此排列的柱子下雨后能接多少雨水。
解题思路(双指针法)
- 维护
leftMax和rightMax,分别表示当前left左侧和right右侧的最大高度; - 若
height[left] < height[right],说明left侧的积水由leftMax决定(右侧有更高挡板); - 否则由
rightMax决定; - 每次处理较低的一侧,确保当前柱子上方的积水可被正确累加。
💡 核心思想:某位置能接的雨水 =
min(左侧最大, 右侧最大) - 当前高度。双指针法通过动态维护边界,避免了预处理左右最大数组。
代码实现
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
if (height.length === 0) return 0;
let left = 0, right = height.length - 1;
let leftMax = 0, rightMax = 0;
let water = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
water += leftMax - height[left];
}
left++;
} else {
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
water += rightMax - height[right];
}
right--;
}
}
return water;
};
复杂度分析
- 时间复杂度:O(n),每个元素访问一次;
- 空间复杂度:O(1)。
总结对比
| 题目 | 核心技巧 | 指针类型 | 关键优化 |
|---|---|---|---|
| 283. 移动零 | 快慢指针紧凑非零元素 | 快慢双指针 | 避免无意义交换 |
| 11. 盛水容器 | 贪心移动矮边 | 对撞双指针 | 面积由矮边决定 |
| 15. 三数之和 | 排序 + 固定一数 + 双指针 | 对撞双指针 | 多重去重 |
| 42. 接雨水 | 动态维护左右最大高度 | 对撞双指针 | 低侧优先处理 |
这四道题充分展示了双指针的多样性与灵活性:从原地重排到组合搜索,从几何最值到动态规划思想的简化。掌握这些模式,不仅能高效攻克 LeetCode 难题,也能在实际工程中写出更优雅、高性能的代码。
希望这篇解析对你有帮助!如果你喜欢这类深入浅出的算法讲解,欢迎关注我的 LeetCode 系列文章 👋