15. 三数之和(3Sum)
🔗 问题简介
📝 题目描述
给你一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足以下条件:
i != j、i != k、j != knums[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[0] + nums[1] + nums[4] = (-1) + 0 + (-1) = -2 ≠ 0
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0。
💡 解题思路
✅ 主流解法:排序 + 双指针(推荐)
步骤详解:
-
排序数组
先对
nums进行升序排序。这一步是为了后续使用双指针,并方便去重。 -
固定第一个数(外层循环)
遍历
i从0到n - 3(因为至少要留两个位置给j和k):- 如果
nums[i] > 0,由于数组已排序,后面不可能再有和为 0 的组合,直接 break。 - 去重处理 :如果
i > 0且nums[i] == nums[i - 1],跳过,避免重复三元组。
- 如果
-
双指针查找另外两个数
设
left = i + 1,right = n - 1:- 计算当前三数之和
sum = nums[i] + nums[left] + nums[right] - 若
sum == 0:- 将
[nums[i], nums[left], nums[right]]加入结果 - 去重 :移动
left跳过相同值;移动right跳过相同值 left++,right--
- 将
- 若
sum < 0→left++ - 若
sum > 0→right--
- 计算当前三数之和
-
返回结果
❌ 其他解法(不推荐)
| 方法 | 描述 | 缺点 |
|---|---|---|
| 暴力三重循环 | 三层 for 循环枚举所有三元组 | 时间复杂度 O(n³),超时 |
| 哈希表优化两重循环 | 固定两个数,用哈希查第三个 | 难以有效去重,逻辑复杂,仍可能 O(n²) 空间 |
💡 结论:排序 + 双指针 是最优解,兼顾时间效率与去重逻辑清晰。
💻 代码实现
java:Java
import java.util.*;
public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < n - 2; i++) {
// 如果最小值都大于0,不可能有解
if (nums[i] > 0) break;
// 跳过重复的第一个元素
if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1, right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
res.add(Arrays.asList(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--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return res;
}
}
go:Go
import (
"sort"
)
func threeSum(nums []int) [][]int {
sort.Ints(nums)
var res [][]int
n := len(nums)
for i := 0; i < n-2; i++ {
if nums[i] > 0 {
break
}
if i > 0 && nums[i] == nums[i-1] {
continue
}
left, right := i+1, n-1
for left < right {
sum := nums[i] + nums[left] + nums[right]
if sum == 0 {
res = append(res, []int{nums[i], nums[left], nums[right]})
// 去重
for left < right && nums[left] == nums[left+1] {
left++
}
for left < right && nums[right] == nums[right-1] {
right--
}
left++
right--
} else if sum < 0 {
left++
} else {
right--
}
}
}
return res
}
🎯 示例演示(以 nums = [-1,0,1,2,-1,-4] 为例)
- 排序后:
[-4, -1, -1, 0, 1, 2] i = 0(-4)→ 最小值-4,最大可能和-4 + 1 + 2 = -1 < 0→ 无解i = 1(-1):left=2(-1),right=5(2)→-1 + (-1) + 2 = 0→ 找到[-1,-1,2]- 移动指针后:
left=3(0),right=4(1)→-1 + 0 + 1 = 0→ 找到[-1,0,1]
i = 2(-1)→ 与i=1相同,跳过(去重)i = 3(0)→0 + 1 + 2 = 3 > 0,无解
✅ 最终结果:[[-1,-1,2], [-1,0,1]]
✅ 答案有效性证明
正确性:
- 完备性 :通过遍历所有可能的
i,并在每个i下用双指针覆盖所有(left, right)组合,确保不漏解。 - 无重复 :
- 外层
i跳过与前一个相同的值; - 内层在找到解后,
left和right同时跳过重复值; - 保证每个三元组
(a, b, c)满足a ≤ b ≤ c且唯一。
- 外层
边界处理:
- 数组长度
< 3→ 自动返回空(循环不执行) - 全零数组 → 正确返回
[[0,0,0]] - 无解情况 → 返回空列表
📊 复杂度分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(n log n) + O(n²) = O(n²) 排序 O(n log n),外层循环 O(n),内层双指针 O(n) |
| 空间复杂度 | O(1)(不计输出空间) 仅使用常数额外变量(Go/Java 的排序可能用 O(log n) 栈空间) |
💡 实际运行效率高,是 LeetCode 官方推荐解法。
📌 问题总结
- 核心思想 :排序 + 双指针 + 去重
- 关键技巧 :
- 利用排序将三数之和转化为"两数之和"问题;
- 通过跳过重复元素避免无效计算和重复答案;
- 提前终止(
nums[i] > 0时 break)优化性能。
- 适用场景:类似"K 数之和"问题(如四数之和)均可借鉴此思路。
- 易错点 :
- 忘记去重导致答案重复;
- 指针移动顺序错误;
- 边界条件(如数组长度不足)未处理。
github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions