跳到正文
第 15 题中等

三数之和

3Sum

在数组中找出所有和为 0 且不重复的三元组,返回三元组列表。

在力扣中练习

题目描述

给你一个整数数组 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

解题思路

主解法

排序 + 双指针

固定第一个数,后两个数用左右指针夹出来

暴力三重循环是 O(n³),而且还要额外去重。先排序,有两个好处:相同的数相邻,方便跳过重复;数组有序后,后两个数可以用双指针。

固定 nums[i],令 left = i+1、right = n-1,看三数之和:

  • 等于 0:记录答案,跳过 left / right 方向的重复值,再同时收拢;
  • 小于 0:需要更大的数,left 右移;
  • 大于 0:需要更小的数,right 左移。

自拟主例 [-2,-1,0,1,2,2]:固定 -2 时,0 和 2 配成 [-2,0,2];固定 -1 时,0 和 1 配成 [-1,0,1]。

分步图解

和 -1 < 0,需要更大的数:left 右移。

sum
-1
nums(已排序)
0
1
2
3
4
5
-2
-1
0
1
2
2
i
left
right
固定 i,left / right 从两端向中间夹
result
[ ]
三元组不能重复
1 / 3
-2 与 0、2 配成第一组;右端还有重复的 2,跳过后再收拢。
分步图解

和 1 > 0,需要更小的数:right 左移。

sum
1
nums(已排序)
0
1
2
3
4
5
-2
-1
0
1
2
2
i
left
right
固定 i,left / right 从两端向中间夹
result
[-2,0,2]
三元组不能重复
1 / 3
固定 -1 时 right 连续左移,直到 0 + 1 配成第二组。

时间 O(n²)(排序 O(n log n) 可忽略),空间 O(1)(不计结果和排序栈)。

时间 O(n²)空间 O(1)

易错点

  • 忘记跳过重复的 i:同一个三元组会被记录多次。
  • 和为 0 后只移动一个指针:会漏掉或重复其他组合;要同时收拢并跳过重复值。
  • 在没排序的数组上用双指针:left/right 的移动方向失去依据。

代码与演示

演示输入nums = [-2,-1,0,1,2,2]

先排序;排序后相同的数相邻,才方便跳过重复三元组。

n
6
nums(已排序)
0
1
2
3
4
5
-2
-1
0
1
2
2
固定 i,left / right 从两端向中间夹
result
[ ]
三元组不能重复
1 / 16
查看 C++ 代码

延伸阅读

  • 三数之和是 k 数之和的基础;四数之和(18)在外层再套一层循环,并做两级去重。
  • 如果结果要求按字典序,排序也顺便保证了每个三元组内部有序。