LeetCode LCR 007. 三数之和
题目描述

题意分析
从整数数组中找出全部和为零的三元组。三个数必须取自不同下标,但它们的数值可以相同;结果按数值组合去重,而不是按下标组合区分。
同一组三个值的不同排列也只算一个答案。需要真正返回全部三元组,若数组不足三个元素或不存在合法组合,返回空结果。
解法:排序后固定一项与双指针
核心思路
[!blue]
先将数组升序排序,使每个三元组都能按非递减顺序表示。然后固定它的第一个位置
i,只在右侧寻找另外两个数,问题就变成有序区间内的两数之和。令
j = i + 1、k = n - 1。若三数和偏小,当前j配上区间最大值仍不够,配更小的右侧数也不可能满足,所以可以右移j。若和偏大,当前k配上区间最小值仍过大,所以可以左移k。每次都淘汰一批确定无解的配对,不会漏掉答案。命中时记录三元组,再收缩两端并跳过刚使用过的重复值。对于固定的
nums[i],某个左值的互补右值已经确定;继续保留相同左值或右值只会得到同样的值组合。数组只是非递减,并非严格递增,因此不能说只移动一端就一定不会再次命中,重复值仍可能命中同一个答案。外层遇到与上一轮相同的
nums[i]时也跳过,避免再次枚举同一个首值。这里保留同值的第一次选择,后面仍可取另一位置上的相同数值;不能先对整个数组去重,也不能因为当前值等于后一个值就把当前候选跳过。当固定值已经大于零时,右侧所有值都不小于它,三数之和不可能再为零,外层可以直接结束。循环始终保持
i < j < k,保证三个下标互异;排序和逐层去重则保证每个值组合只输出一次。
解题步骤
- 升序排序,枚举还能给后面留下两个位置的固定下标
i。- 固定值为正时结束;与上一轮首值相同则跳过当前轮。
- 在
i右侧初始化相向双指针,比较三数和。- 和偏小移动
j,偏大移动k;等于零则保存三元组。- 命中后两端各移动一次,再分别越过刚使用值的重复项,继续寻找不同组合。
- 完成所有固定位置后返回结果。
代码实现
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 排序是全部推理的地基:既统一了三元组内部顺序,又提供了单调性。
Arrays.sort(nums);
List<List<Integer>> answer = new ArrayList<>();
int n = nums.length;
// nums[i] 是三元组里最小的数,一旦为正后面必然无解。
for (int i = 0; i < n - 2 && nums[i] <= 0; ++i) {
// 与前一个值相同则跳过,避免以同一个最小值重复搜索。
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int j = i + 1;
int k = n - 1;
while (j < k) {
int x = nums[i] + nums[j] + nums[k];
if (x < 0) {
++j;
} else if (x > 0) {
--k;
} else {
answer.add(List.of(nums[i], nums[j++], nums[k--]));
// 跳过与刚用过的值相同的元素,两侧都要跳。
while (j < k && nums[j] == nums[j - 1]) {
++j;
}
while (j < k && nums[k] == nums[k + 1]) {
--k;
}
}
}
}
return answer;
}
}
import (
"sort"
)
func threeSum(nums []int) (answer [][]int) {
sort.Ints(nums)
n := len(nums)
for i := 0; i < n-2 && nums[i] <= 0; i++ {
if i > 0 && nums[i] == nums[i-1] {
continue
}
j, k := i+1, n-1
for j < k {
x := nums[i] + nums[j] + nums[k]
if x < 0 {
j++
} else if x > 0 {
k--
} else {
answer = append(answer, []int{
nums[i],
nums[j],
nums[k],
})
j, k = j+1, k-1
for j < k && nums[j] == nums[j-1] {
j++
}
for j < k && nums[k] == nums[k+1] {
k--
}
}
}
}
return
}
复杂度分析
设数组长度为 $n$,答案三元组数量为 $r$。
- 时间复杂度:$O(n^2)$。排序需要 $O(n\log(n+1))$,每个固定位置的双指针只进行一次线性收缩。
- 辅助空间复杂度:双指针部分为 $O(1)$,整体额外空间取决于排序实现;返回结果另占 $O(r)$,最坏可达平方级。
关键点总结
[!green]
- 排序同时提供端点移动的单调依据和唯一的三元组内部顺序。
- 固定首值后,剩余问题是有序两数之和。
- 同一枚举层跳过重复值,不等于删除输入中的重复元素。
- 首值为正即可停止,严格下标顺序自然排除复用同一位置。
易错点总结
[!yellow]
- 外层与前一个已处理值比较去重,不要因等于后一个值就跳过,后者可能漏掉需要两个同值的答案。
- 命中后需要推进指针,随后越过重复值,避免再次输出相同三元组。
- 指针比较使用
j < k,相遇时不能把同一下标作为两个数。- 原数组未排序时,不能根据和的大小直接移动两端。
- 结果按值去重,但每个值仍需要一个实际位置,不能通过预先删除重复元素简化输入。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 16. 最接近的三数之和 | 中等 | 同样排序后固定一项并对撞指针,原题找最接近的和,本题输出恰好为0的全部不重复组合。 |
| 18. 四数之和 | 中等 | 把固定一个数扩展为固定两个数,内层仍复用双指针和重复值处理。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!