LeetCode 16. 最接近的三数之和:排序 + 双指针,如何“逼近答案”

10 阅读3分钟

一、题目回顾

给定一个整数数组 nums 和一个目标值 target
从数组中选出 三个整数,使它们的和 最接近 target

要求返回这三个数的和。

示例:

nums = [-1, 2, 1, -4]
target = 1

输出:2

解释:

(-1) + 2 + 1 = 2

二、第一反应:暴力枚举能做吗?

最直接的想法是三重循环:

for i
  for j
    for k

时间复杂度:O(n³)

问题在于:

  • n 稍微大一点就直接超时
  • 而且每次只是“更接近一点”,却要遍历所有组合

所以关键问题变成:

能不能在枚举三元组时,有方向地靠近 target


三、核心突破口:排序

Arrays.sort(nums);

排序的目的只有一个:

让“移动指针”这件事,具有明确的方向感

排序后,数组满足:

nums[left] 向右 → 变大
nums[right] 向左 → 变小

这为双指针提供了成立的基础。


四、整体思路拆解

整体策略可以拆成三步:

  1. 排序数组
  2. 固定一个数 nums[i]
  3. 在剩余区间内,用双指针寻找最接近的两数之和

一句话总结:

三数问题 → 固定一个数 → 转化为“有序数组上的两数逼近问题”


五、完整代码

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int n = nums.length;

        int closestSum = nums[0] + nums[1] + nums[2];

        for (int i = 0; i < n - 2; i++) {
            int left = i + 1;
            int right = n - 1;

            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];

                if (Math.abs(sum - target) < Math.abs(closestSum - target)) {
                    closestSum = sum;
                }

                if (sum < target) {
                    left++;
                } else if (sum > target) {
                    right--;
                } else {
                    return target;
                }
            }
        }

        return closestSum;
    }
}

下面逐段拆解这段代码在“想什么”。


六、为什么 closestSum 要这样初始化?

int closestSum = nums[0] + nums[1] + nums[2];

原因很简单:

  • 题目保证一定存在答案
  • 我们需要一个 合法的初始对照值
  • 排序后取前三个数,是最自然、最安全的选择

这样后续只需要比较:

当前 sum 是否比 closestSum 更接近 target

七、固定一个数,本质在干嘛?

for (int i = 0; i < n - 2; i++) {
    int left = i + 1;
    int right = n - 1;

这一步的含义是:

  • 固定 nums[i]
  • 在区间 [i+1, n-1]
  • 找两个数,使三数之和最接近 target

也就是说:

三数问题 → 多次“两数最接近”问题


八、为什么每一步都要更新最优解?

if (Math.abs(sum - target) < Math.abs(closestSum - target)) {
    closestSum = sum;
}

注意一点:

这题不是找“等于 target”的解,
而是找“距离最小”的解。

所以策略是:

只要当前结果更近,就立刻更新

哪怕后面还能继续移动指针,也不影响当前最优值的记录。


九、双指针为什么这样移动?

if (sum < target) {
    left++;
} else if (sum > target) {
    right--;
}

这是整个算法最核心的“直觉”。

  • sum < target
    → 当前和偏小
    → 想变大,只能让 nums[left] 变大
    left++
  • sum > target
    → 当前和偏大
    → 想变小,只能让 nums[right] 变小
    right--

排序保证了这一步 一定是朝着目标逼近的


十、为什么遇到刚好等于可以直接返回?

else {
    return target;
}

因为:

|sum - target| = 0

这是理论上的最优解,不可能再被超越。


十一、为什么这题不需要去重?

这是很多人从「三数之和」转过来时最容易困惑的点。

原因是:

  • 我们只关心 最接近的值
  • 不关心具体是哪一组数
  • 重复组合不会影响“距离最小值”的判断

而在「三数之和 = 0」中:

  • 要返回所有不同组合
  • 去重是必须的

十二、复杂度分析

  • 时间复杂度:O(n²)

    • 外层固定一个数
    • 内层双指针线性扫描
  • 空间复杂度:O(1)

    • 除排序外只使用常量空间

十三、总结

这道题真正想教会我们的不是“三个数怎么加”,而是:

在有序数组中,如何用双指针进行“单调逼近”