题目
给你一个整数数组 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 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[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]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000-105 <= nums[i] <= 105
Java语言
java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
// 跳过重复的第一个数
if (i > 0 && nums[i] == nums[i - 1]) continue;
// 剪枝:最小的三个数都 > 0,不可能和为 0
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
// 剪枝:当前数 + 最大的两个数都 < 0,跳过当前数
if (nums[i] + nums[nums.length - 2] + nums[nums.length - 1] < 0) continue;
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
left++;
} else if (sum > 0) {
right--;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 跳过重复的 left
while (left < right && nums[left] == nums[left + 1]) left++;
// 跳过重复的 right
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
return result;
}
}
核心思路: 排序 + 双指针。先排序,固定一个数 nums[i],然后用双指针在剩余部分找两数之和等于 -nums[i]。
逐行说明:
java
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
创建结果列表。先排序是双指针的前提,排序后重复值相邻,也方便去重和剪枝。
java
for (int i = 0; i < nums.length - 2; i++) {
遍历第一个数,到 length - 2 停止(至少留两个位置给双指针)。
java
if (i > 0 && nums[i] == nums[i - 1]) continue;
去重 :如果当前数和前一个相同,跳过,避免产生重复三元组。例如 [-1,-1,0,1],第一个 -1 找了 [-1,0,1],第二个 -1 跳过。
java
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
if (nums[i] + nums[nums.length - 2] + nums[nums.length - 1] < 0) continue;
剪枝优化:
- 最小的三个数之和都 > 0,后面不可能有更小的了,直接
break - 当前数加上最大的两个数都 < 0,当前数太小,
continue换下一个
java
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
双指针,left 从 i+1 开始,right 从末尾开始。计算三数之和。
java
if (sum < 0) {
left++;
} else if (sum > 0) {
right--;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
- 和 < 0:太小了,
left右移让和变大 - 和 > 0:太大了,
right左移让和变小 - 和 == 0:找到一组,加入结果
java
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
去重 :跳过与当前 left/right 相同的值,然后各移动一步继续找下一组。
示例 1 演示: nums = [-1,0,1,2,-1,-4] → 排序后 [-4,-1,-1,0,1,2]
| i | numsi | left | right | sum | 操作 | result |
|---|---|---|---|---|---|---|
| 0 | -4 | 1 | 5 | -4+(-1)+2=-3 | <0, left++ | \[\] |
| -4 | 2 | 5 | -4+(-1)+2=-3 | <0, left++ | \[\] | |
| -4 | 3 | 5 | -4+0+2=-2 | <0, left++ | \[\] | |
| -4 | 4 | 5 | -4+1+2=-1 | <0, left++ → left>=right 退出 | \[\] | |
| 1 | -1 | 2 | 5 | -1+(-1)+2=0 | 找到 | \[-1,-1,2] |
| -1 | 3 | 4 | -1+0+1=0 | 找到 | \[-1,-1,2,-1,0,1] | |
| 2 | -1 | (与i=1相同,skip) | continue | |||
| 3 | 0 | 4 | 5 | 0+1+2=3 | >0, right-- → 退出 |
最终返回 [[-1,-1,2],[-1,0,1]] ✓
时间复杂度 O(n²),空间复杂度 O(1)(不计排序)。
python语言
python
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
result = []
nums.sort()
for i in range(len(nums) - 2):
# 跳过重复的第一个数
if i > 0 and nums[i] == nums[i - 1]:
continue
# 剪枝:最小的三个数都 > 0,不可能和为 0
if nums[i] + nums[i + 1] + nums[i + 2] > 0:
break
# 剪枝:当前数 + 最大的两个数都 < 0,跳过当前数
if nums[i] + nums[-2] + nums[-1] < 0:
continue
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过重复的 left
while left < right and nums[left] == nums[left + 1]:
left += 1
# 跳过重复的 right
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return result
逻辑与 Java 版完全一致,逐行说明:
python
result = []
nums.sort()
创建结果列表,nums.sort() 原地排序。对应 Java 的 Arrays.sort(nums)。
python
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
遍历第一个数。nums[i] == nums[i - 1] 跳过重复值,对应 Java 的 nums[i] == nums[i - 1]。
python
if nums[i] + nums[i + 1] + nums[i + 2] > 0:
break
if nums[i] + nums[-2] + nums[-1] < 0:
continue
剪枝优化。nums[-2] 和 nums[-1] 是 Python 的负索引,直接取倒数第二和最后一个元素,比 Java 的 nums[nums.length - 2] 简洁很多。
python
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
双指针。left, right = i + 1, len(nums) - 1 一行同时赋值两个变量。
python
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
- 和 < 0:
left右移 - 和 > 0:
right左移 - 和 == 0:
result.append([a, b, c])添加列表,对应 Java 的Arrays.asList(a, b, c)
python
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
跳过重复值后,各移动一步继续找。+= 1 / -= 1 对应 Java 的 ++ / --。
与 Java 版的关键区别:
| 特性 | Java | Python |
|---|---|---|
| 排序 | Arrays.sort(nums) |
nums.sort() |
| 列表长度 | nums.length |
len(nums) |
| 添加元素 | result.add(Arrays.asList(...)) |
result.append([...]) |
| 负索引 | nums[nums.length - 1] |
nums[-1] |
| 双变量赋值 | int left = i+1, right = n-1; |
left, right = i+1, n-1 |
| 自增/自减 | left++ / right-- |
left += 1 / right -= 1 |
时间复杂度 O(n²),空间复杂度 O(1)(不计排序)。