描述
给你一个整数数组 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 。
题解
暴力枚举
如果说使用暴力解决问题,那就是从 nums[0]+nums[1]+nums[2],sums[0]+nums[1]+nums[3],…nums[n-2]+nums[n-1]+nums[n]。
按照这种写法也就是如下伪代码
for(int i = 0,i < n-2,i++){
for(int j = 1,j<n-1,j++){
for(int k = 2,k<n,k++){
if(nums[i] + nums[j] + nums[k]) ==0{
list.add(i,j,k)
}
}
}
}
这么算下来复杂度就是O(n^3),后续还要去重操作,又是一个很大的工作量,所以这种方式明显不适合。
排序+双指针(官方)
根据暴力解法我们看下,如果我们先排序好,然后从 num[0]
开始遍历,取出值和 0 做减法,然后再去数组中找到和是-nums[0]
的两个数字,这就有点像两数之和的解决方案了。那么这块我们就可以使用双指针的方法来取出余下的两个数字了。
首先,我们定义 index
,即当前取出的是第几个元素,然后令 left
为 index+1
,right
为数组的最后一位即nums.length-1
,然后判断 left+right == -index
,然后我们每次给 index++的时候,判断一下++之后对应的值是不是和 index 的值一致,如果一致的话我们就继续++,因为题目要求我们结果的元素不能重复。那么我们来看一下代码怎么实现:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length; //整个数组的长度
// 使用 JavaApi 排序,不用自己实现排序方法了。
Arrays.sort(nums);
// 最后输出的 list
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 枚举 index
for (int index = 0; index < n; ++index) {
// 需要和上一次枚举的数不相同,因为进循环之前index 已经++,所以与前面一个值比较
if (index > 0 && nums[index] == nums[index - 1]) {
continue;
}
// right 对应的指针初始指向数组的最右端
int right = n - 1;
int target = -nums[index]; // left+right目标值,
// 枚举 left
for (int left = index + 1; left < n; ++left) {
// 同样的,左侧的值也需要和上一次左侧枚举的数不相同,如果相同,那么 right 肯定也是同一个值
if (left > index + 1 && nums[left] == nums[left - 1]) {
continue;
}
// 需要保证 left 的指针在 right 的指针的左侧
while (left < right && nums[left] + nums[right] > target) {
--right;
}
// 如果指针重合,随着 left 后续的增加就不会有满足 index + left + right=0,可以退出循环
if (left == right) {
break;
}
// 如果找到目标值,加入 list 中存住
if (nums[left] + nums[right] == target) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[index]);
list.add(nums[left]);
list.add(nums[right]);
ans.add(list);
}
}
}
return ans;
}
}
排序+双指针(约束)
看了上面的代码,是不是有一种豁然开朗的感觉,但是总感觉有些场景还是多余了,比如[1,2,4,4],这个数组,就永远没有解。而上面的代码还是走了好几遍循环,这里我们可以简单的进行约束一下
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 最后输出的 list
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 如果数组为空或者长度不够,直接返回空 list
if (nums == null || nums.length < 3){
return ans;
}
// 使用 JavaApi 排序,不用自己实现排序方法了。
Arrays.sort(nums);
int n = nums.length; //整个数组的长度
// 枚举 index
for (int index = 0; index < n; ++index) {
// 最小的数字都大于 0,其他两位数字相加就不可能小于 0,直接推出循环
if (nums[index] > 0){
break;
}
// 需要和上一次枚举的数不相同,因为进循环之前index 已经++,所以与前面一个值比较
if (index > 0 && nums[index] == nums[index - 1]) {
continue;
}
// right 对应的指针初始指向数组的最右端
int right = n - 1;
int target = -nums[index]; // left+right目标值
// 枚举 left
for (int left = index + 1; left < n; ++left) {
// 同样的,左侧的值也需要和上一次左侧枚举的数不相同,如果相同,那么 right 肯定也是同一个值
if (left > index + 1 && nums[left] == nums[left - 1]) {
continue;
}
// 需要保证 left 的指针在 right 的指针的左侧
while (left < right && nums[left] + nums[right] > target) {
--right;
}
// 如果指针重合,随着 left 后续的增加就不会有满足 index + left + right=0,可以退出循环
if (left == right) {
break;
}
// 如果找到目标值,加入 list 中存住
if (nums[left] + nums[right] == target) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[index]);
list.add(nums[left]);
list.add(nums[right]);
ans.add(list);
}
}
}
return ans;
}
}
以上就是本人对这题的一点小看法,感谢力扣官方题解以及社区各位大神的解析。每天学会一题,工资早日 double!