题目描述

LeetCode 15. 三数之和

给定一个整数数组 nums,判断是否存在三个元素 a, b, c,使得 a + b + c = 0

找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
​
输入:nums = [0,1,1]
输出:[]
​
输入:nums = [0,0,0]
输出:[[0,0,0]]

学习历程(从暴力到优化)

第一步:分析暴力解法

第一反应:三重循环枚举所有三元组

for i in range(n):
    for j in range(i+1, n):
        for k in range(j+1, n):
            if nums[i] + nums[j] + nums[k] == 0:
                记录结果

复杂度分析:

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

  • 空间复杂度:O(1)(不考虑结果存储)

问题

  1. 太慢了,n=3000时根本跑不完

  2. 去重很麻烦,需要额外处理


第二步:联想两数之和

思考:如果只有两数之和(找 a + b = target),有什么更好的做法?

方案一:哈希表(O(n)时间)

  • 遍历数组,把每个数存入HashSet

  • 对于当前数 x,检查 target - x 是否在Set中

方案二:排序+双指针(O(nlogn)时间)

  • 先排序

  • 左指针指向开头,右指针指向结尾

  • 根据 sum 和 target 的大小关系移动指针


第三步:关键洞察——降维思想

三数之和 a + b + c = 0 可以变形为:b + c = -a

策略

  1. 固定第一个数 a(遍历数组)

  2. 在剩下的区间找两数之和等于 -a

  3. 这就转化成了「两数之和」问题!

复杂度预估

  • 固定第一个数:O(n)

  • 每次找两数之和:O(n)(双指针)

  • 总复杂度:O(n²)

比暴力 O(n³) 提升了一个数量级!


第四步:排序+双指针详解

为什么要先排序?

排序后可以使用双指针技巧,而且重复数字会相邻,方便去重。

双指针怎么工作?

假设数组已排序:[-4, -1, -1, 0, 1, 2]

例:找两数之和 = 0

初始:L=0(-4), R=5(2)
sum = -4 + 2 = -2 < 0
→ sum太小了,需要变大,L右移(让负数变小)
​
L=1(-1), R=5(2)
sum = -1 + 2 = 1 > 0
→ sum太大了,需要变小,R左移
​
L=1(-1), R=4(1)
sum = -1 + 1 = 0 ✓ 找到了!

指针移动规则

  • sum < target:L右移(需要更大的数)

  • sum > target:R左移(需要更小的数)

  • sum == target:找到了!L++ 且 R--(继续找其他组合)


第五步:去重处理(难点)

排序后重复数字相邻,比如:[-1, -1, -1, 0, 1, 2]

三个去重点

  1. 固定的数去重

    if i > 0 && nums[i] == nums[i-1] {
        continue;  // 跳过重复的固定值
    }
  2. 左指针去重

    while (L < R && nums[L] == nums[L+1]) L++;
  3. 右指针去重

    while (L < R && nums[R] == nums[R-1]) R--;

完整代码实现(Java)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
​
public class ThreeSum {
​
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
​
        // 1. 排序(O(nlogn))
        Arrays.sort(nums);
​
        int n = nums.length;
​
        // 2. 固定第一个数
        for (int i = 0; i < n - 2; i++) {
            // 剪枝:如果当前数已经大于0,后面的都大于0,不可能和为0
            if (nums[i] > 0) {
                break;
            }
​
            // 去重:跳过重复的固定值
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
​
            // 3. 双指针找另外两个数
            int target = -nums[i];  // 需要找的两数之和
            int left = i + 1;       // 左指针
            int right = n - 1;      // 右指针
​
            while (left < right) {
                int sum = nums[left] + nums[right];
​
                if (sum < target) {
                    // 太小了,左指针右移
                    left++;
                } else if (sum > target) {
                    // 太大了,右指针左移
                    right--;
                } else {
                    // 找到了!
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
​
                    // 去重:跳过重复的左指针值
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    // 去重:跳过重复的右指针值
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
​
                    // 继续寻找其他组合
                    left++;
                    right--;
                }
            }
        }
​
        return result;
    }
​
    // 测试
    public static void main(String[] args) {
        ThreeSum solution = new ThreeSum();
​
        int[] nums = {-1, 0, 1, 2, -1, -4};
        List<List<Integer>> result = solution.threeSum(nums);
​
        System.out.println("结果:" + result);
        // 输出:[[-1, -1, 2], [-1, 0, 1]]
    }
}

复杂度分析

指标

复杂度

说明

时间复杂度

O(n²)

排序O(nlogn) + 双指针遍历O(n²)

空间复杂度

O(1)

不考虑结果存储,只使用常数额外空间


关键总结

解题模板(排序+双指针)

1. 排序数组
2. 固定一个数(遍历)
3. 双指针在剩余区间查找
4. 根据sum大小移动指针
5. 注意三个地方的去重

类似题目

题目

核心思想

两数之和

哈希表 / 双指针

三数之和

固定一个 + 双指针

四数之和

固定两个 + 双指针

最接近的三数之和

双指针 + 维护最小差值

面试技巧

  1. 先讲暴力解:展示思路完整性,至少能得部分分

  2. 引导到优化:面试官问"能不能优化"时,提排序+双指针

  3. 手写去重逻辑:这是考察重点,要写得干净利落

  4. 分析复杂度:证明你的解法是正确的最优解


易错点复盘

  1. 忘记排序:双指针必须在有序数组上工作

  2. 去重逻辑漏了:三个地方都要去重(固定值、左指针、右指针)

  3. 指针越界left < right 别写成 left <= right

  4. sum计算错误:注意负数的情况,比如 -1 + 2 = 1 不是 -3