LeetCode 热题 100:双指针

56 阅读4分钟

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 = 0right = 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

解题思路

  1. 排序:便于去重和使用双指针;
  2. 固定第一个数 nums[i] ,问题转化为“在 [i+1, n-1] 中找两数之和为 -nums[i]”;
  3. 双指针查找left = i+1right = n-1,根据和的大小调整指针;
  4. 跳过重复值:避免结果重复(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 系列文章 👋