给你一个整数数组
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 。
非常经典的一道题
最简单的思路一定是挨个遍历。
找到一个元素,找第二个元素,然后再找第三个元素。
这相当于遍历三遍叠加。时间复杂度是O(n^3),是超时的。
本篇中主要解析排序+双指针的解法。
为什么两数之和那道题不能排序?因为那道题要返回下标索引,排序会打乱索引。所以不能排序。
而这道题只需要返回值。即使打乱也无妨。最后返回对的值就可以。
整体思路
对整个数组进行排序。
排序后。我固定第一个元素。
比如i = 0。固定一个元素后,初始化left和right指针,分别为i + 1和n - 1(n为数组长度)。
这两个数是最大和最小的数了。
如果这时候这三个数的和大于0,那么我把right指针左移。和会变小。
如果三个数的和小于0,把left右移,和会变大。
这样我就可以通过双指针来快速找到这三个数。
这个思路其实是容易理解的。
核心代码如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 排序
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0;i < n - 2;i++) {
// 如果三个最小的数和都大于0,那就没有找的必要了
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) {
continue;
}
// 如果三个最大的数和都小于0,也没有找的必要。
if (nums[i] + nums[n - 1] + nums[n - 2] < 0) {
continue;
}
// 初始化左右指针
int l = i + 1;
int r = n - 1;
while(l < r) {
// 如果和大于0,移动右指针,小于0,移动左指针。等于0,那么就找到了。
if (nums[l] + nums[i] + nums[r] > 0) {
r--;
} else if (nums[l] + nums[i] + nums[r] < 0) {
l++;
} else {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[l]);
list.add(nums[r]);
ans.add(list);
l++;
r--;
}
}
}
return ans;
}
}
但是还有一个重要的问题——去重。
我们要排除重复的元素。
去重主要有两个场景。
一个是基准元素。
比如我定下基准元素为1,下一个元素还是1,那我就不用找下一个了。因为重复了。
另一个是左右指针的元素。
假如有一个组合式是[-3, 1, 2]
左指针为1,如果下一个也是1,我就不用看了。
右指针也同理,前一个相同也不用看了。
所以最终代码修改如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 排序
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0;i < n - 2;i++) {
// 去重基准元素
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 如果三个最小的数和都大于0,那就没有找的必要了
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) {
continue;
}
// 如果三个最大的数和都小于0,也没有找的必要。
if (nums[i] + nums[n - 1] + nums[n - 2] < 0) {
continue;
}
// 初始化左右指针
int l = i + 1;
int r = n - 1;
while(l < r) {
// 如果和大于0,移动右指针,小于0,移动左指针。等于0,那么就找到了。
if (nums[l] + nums[i] + nums[r] > 0) {
r--;
} else if (nums[l] + nums[i] + nums[r] < 0) {
l++;
} else {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[l]);
list.add(nums[r]);
ans.add(list);
// 去重左右指针
while (l < r && nums[l] == nums[l + 1]) {
l++;
}
while (l < r && nums[r] == nums[r - 1]) {
r--;
}
l++;
r--;
}
}
}
return ans;
}
}