给你一个整数数组 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
这个题深度优先最难的地方莫过于避免重复。此处可能出现两种重复的情况:
1️⃣ 同一个元素多次使用。在深度优先遍历过程当中,多次使用同一个相同的元素多次导致的重复
这种剪枝的方法和算法设计三角形剪枝是一种思路,每次深度优先遍历的时候引入start作为基,然后深度优先遍历的时候以i+1作为下一层递归树的启始。
因为每一层都是从上一层走到的位置的下一个位置开始的,因此不会出现重复的情况。
2️⃣ 同一元素多次出现,避免多次使用
这种剪枝比较麻烦,因为同一元素多次出现,为了方便起见,我们将重复元素聚集在一起(排序一下)。然后是重点:我们只保留dfs之后启始位置的重复元素,其余直接跳过。即跳过(i>start &&nums[i] == nums[i-1])的元素。
为什么是这样剪枝呢?
这是因为我们都知道重复元素成堆出现的地方,第一个元素启始位置可以出现取到所有出现情况,所以仅保留第一个元素的dfs结果即可。同理,到了下一层递归树,也只需要保留第一个的结果。以此类推,后出现的都不需要。所以满足条件的直接跳过即可。
为什么是nums[j] == nums[j-1]时跳过,而不是nuts[j] == nums[j+1]跳过呢?这是因为如果后者会直接把第一个元素跳过,与我们的做法不符合
class Solution {
List<List<Integer>> ans = new LinkedList<>();
List<Integer> tmp = new LinkedList<>();
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
int[] used = new int[nums.length];
dfs(nums,used,0,0);
return ans;
}
public void dfs(int[] nums,int[] used,int res,int start){
if(tmp.size() == 3 && res == 0){
ans.add(new LinkedList<>(tmp));
return;
}
for(int i=start;i<nums.length;i++){
if(i>start && nums[i] == nums[i-1]) continue;
if(tmp.size() < 3 && used[i] == 0){
res += nums[i];
tmp.add(nums[i]);
used[i] = 1;
dfs(nums,used,res,i+1);
used[i] = 0;
tmp.remove(tmp.size()-1);
res -= nums[i];
}
}
}
}
这道题是看着 灵茶山艾府 大佬的思路做的,先看下代码:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < n - 2; ++i) {
int x = nums[i];
if (i > 0 && x == nums[i - 1]) continue; // 跳过重复数字
if (x + nums[i + 1] + nums[i + 2] > 0) break; // 优化一
if (x + nums[n - 2] + nums[n - 1] < 0) continue; // 优化二
int j = i + 1, k = n - 1;
while (j < k) {
int s = x + nums[j] + nums[k];
if (s > 0) --k;
else if (s < 0) ++j;
else {
ans.add(List.of(x, nums[j], nums[k]));
for (++j; j < k && nums[j] == nums[j - 1]; ++j); // 跳过重复数字
for (--k; k > j && nums[k] == nums[k + 1]; --k); // 跳过重复数字
}
}
}
return ans;
}
}
具体做法是怎么的呢?
首先确定一个启始指针 i ,同时利用上面去重的方法去重。
当连续的三个元素之和>0时,说明后面的元素加上一定会过大(递增数组),所以直接break,因为没有可行解。
当这个元素加上最后两个元素之和<0,说明还需要第一个元素向前,直接continue。
剩下的情况就是一般情况了。让j 指针从i+1开始,k从最后一个位置开始。在 j
for (++j; j < k && nums[j] == nums[j - 1]; ++j); // 跳过重复数字
for (--k; k > j && nums[k] == nums[k + 1]; --k); // 跳过重复数字
这里k是倒叙遍历的,所以保住最右边的元素,用nums[k] == nums[k + 1]作为条件。
为什么在找到结果才去重呢?是因为没找到结果重复不影响最终的输出。