大家好,我是你们的算法小伙伴。今天我们来练习一道经典的中等难度题 ------LeetCode 15. 三数之和。这道题是「两数之和」的进阶版,核心考察排序 + 双指针 的综合应用,同时重点考察去重逻辑的处理,是面试中非常高频的算法题。
题目描述
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。
请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0不同的三元组是 [-1,0,1] 和 [-1,-1,2]。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
提示:
3 <= nums.length <= 3000-10^5 <= nums[i] <= 10^5
解题思路
核心思路转化
暴力枚举三元组的时间复杂度为 O(n^3),对于 n=3000 的规模完全无法通过。优化方向 :将「三数之和」转化为「两数之和」,利用排序 + 双指针将复杂度优化到 O(n^2)。
解题步骤
- 排序:先对数组排序,为双指针和去重打下基础。
- 固定一个数 :遍历数组,将每个元素
nums[i]作为第一个数,目标转化为在剩余元素中找两数之和等于-nums[i]。 - 双指针找两数之和 :在
i+1到n-1区间内,用左右指针寻找满足条件的组合。 - 关键去重 :这是本题的难点,必须对
nums[i]、left、right对应的元素进行精准去重。
代码实现
排序 + 双指针法
java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
int n = nums.length;
// 1. 排序(关键步骤)
Arrays.sort(nums);
// 2. 遍历固定第一个数
for (int i = 0; i < n; i++) {
// 去重:如果当前数和前一个数相同,跳过(避免重复三元组)
if (i > 0 && nums[i] == nums[i-1]) {
continue;
}
// 优化:如果第一个数已经大于0,后续不可能和为0,直接返回
if (nums[i] > 0) {
return result;
}
int target = -nums[i];
int left = i + 1;
int right = n - 1;
// 3. 双指针找两数之和
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
// 找到一组解,加入结果集
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--;
} else if (sum < target) {
// 和太小,左指针右移
left++;
} else {
// 和太大,右指针左移
right--;
}
}
}
return result;
}
}
代码详解
一、排序
Arrays.sort(nums) 是核心前提,不仅让双指针移动有方向,还为去重提供了依据(重复元素相邻)。
二、固定第一个数与去重
- 去重逻辑 :
if (i > 0 && nums[i] == nums[i-1]) continue。- 如果当前数和前一个数相同,说明前一个数已经处理过该组合,跳过以避免重复。
- 剪枝优化 :
if (nums[i] > 0) return result。- 数组有序,若第一个数为正,后续数均为正,三者之和不可能为 0,直接终止循环。
三、双指针找两数之和
- 指针初始化 :
left = i + 1(避开固定数),right = n - 1(数组末尾)。 - 指针移动 :
sum == target:找到解,记录并去重,然后同时移动指针。sum < target:需要增大和,左指针右移。sum > target:需要减小和,右指针左移。
- 内部去重 :找到解后,必须跳过
left和right指向的重复元素,防止结果集中出现重复三元组。
示例 1 模拟
输入:nums = [-1,0,1,2,-1,-4]排序后:[-4, -1, -1, 0, 1, 2]
- i=0 (nums[i]=-4) ,target=4:
- left=1 (-1), right=5 (2) → sum=1 < 4 → left++
- left=2 (-1), right=5 (2) → sum=1 < 4 → left++
- left=3 (0), right=5 (2) → sum=2 < 4 → left++
- left=4 (1), right=5 (2) → sum=3 < 4 → left++ (left >= right,结束)
- i=1 (nums[i]=-1) ,target=1:
- left=2 (-1), right=5 (2) → sum=1 == 1 → 记录
[-1,-1,2] - 去重:left 移至 3 (0),right 移至 4 (1)
- left=3 (0), right=4 (1) → sum=1 == 1 → 记录
[-1,0,1]
- left=2 (-1), right=5 (2) → sum=1 == 1 → 记录
- i=2 (nums[i]=-1):与 i=1 重复,跳过。
- i=3 (nums[i]=0) :后续和均 >= 0,无结果。最终结果:
[[-1,-1,2], [-1,0,1]]
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 排序 + 双指针 | O(n^2) | O(logn) (排序栈空间) | 排序 O(nlogn),遍历 O(n^2),主导项为 O(n^2) |
总结
- 核心考点 :本题是排序 + 双指针的经典组合,是面试中 O(n2) 复杂度算法的典型代表。
- 去重是关键 :这道题 90% 的错误都出在去重逻辑上。必须牢记对
i、left、right三处的重复元素都要进行跳过。 - 剪枝优化:固定数大于 0 时直接返回,是非常实用的优化,能减少不必要的计算。
- 与两数之和的区别 :
- 两数之和:用哈希表或双指针。
- 三数之和:必须排序 + 双指针,且重点在去重。
今天的每日算法练习就到这里,我们明天再见!👋