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 = 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 。
算法思路
这个题和两数之和十分相似, 不同的是, 题目要找的是"不重复"的三元组(元素不同), 那么我们可以在两数之和的双指针思想上进行解题:
- 先排序
- 先固定一个数, 加入这个数从左到右枚举到了
i
位置 - 然后定义两个指针, 指向
i
位置右边区间的两端,left = i + 1, right = nums.length - 1
, 在[left, right]
区间内找到两数之和为-nums[i]
的二元组.- 这里要注意的是, 当我们固定的数是正数
a
时, 我们需要在右侧区间寻找两数之和为-a
的二元组, 但是右侧区间的数都大于等于a
, 都是正数, 所以根本不可能找到两数之和为负数的二元组, 所以当枚举到正数时, 就可以直接break
.
- 这里要注意的是, 当我们固定的数是正数
- 找到了就和
nums[i]
组成符合题目要求的三元组
但是要注意的是, 这道题有去重操作
假设nums
是这样一个数组[-4, -4, -1, 0, 0, 0, 1, 1, 4, 4, 5, 6]
如果现在到了这种情况:[-4, 0, 4]
已经计入了返回的结果集里面.
left++, right--
后, 虽然形成的三元组依然符合题意, 但是这和上一个结果就重复了, 而题目要求不能重复. 所以, 指针移动的操作中, 要"跳过重复"的元素.
跳过符合两数之和为
-num[i]
的重复元素. 而那些不符合却重复的数不用管, 因为它本身也进入不到结果集.
不仅如此, 我们再看, 假如第一个-4
右边的区间已经遍历完成, i++
, 但是新的固定值和前一个固定值相同, 如果我们不做处理, 依然在它右边找两数之和是4
的元素, 也会产生重复. 所以这里也要"跳过重复"的元素
因此
- 找到⼀个结果之后,
left
和right
指针要「跳过重复」的元素; - 当使⽤完⼀次双指针算法之后,固定的
a
也要「跳过重复」的元素。
Java代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> lists = new LinkedList<>();
int i = 0;
//为什么是i < nums.length - 2, 因为我们要找的是三元组
//最后判断的一个三元组是nums[nums.length -3], nums[nums.length - 2], nums[nums.length - 1]
while(i < nums.length - 2) {
//如果固定的数字是正数, 那么右侧绝对不会出现两数之和为负数的二元组, 所以可以直接终止循环
if(nums[i] > 0) {
break;
}
int left = i + 1;
int right = nums.length - 1;
while(left < right) {
int sum = nums[left] + nums[right];
if(sum > -nums[i]) {
right--;
} else if(sum < -nums[i]) {
left++;
} else {
lists.add(new LinkedList<Integer>(Arrays.asList(nums[i], nums[left], nums[right])));
right--;
left++;
//跳过重复元素, 注意left < right
while(left < right && nums[left] == nums[left - 1]) {
left++;
}
while(left < right && nums[right] == nums[right + 1]) {
right--;
}
}
}
i++;
//跳过重复的数字
while (i < nums.length && nums[i] == nums[i - 1]) {
i++;
}
}
return lists;
}
}
时间复杂度: O(N2) 空间复杂度:O(log2N)