LeetCode Hot100 15.三数之和

文章目录

      • 题目描述
      • 回溯法(解不了,超时了,参考下剪枝)
      • 双指针循环

题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != 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从最后一个位置开始。在 jList.of())方法将答案装入ans数组。装入后跳过重复

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]作为条件。

为什么在找到结果才去重呢?是因为没找到结果重复不影响最终的输出。

你可能感兴趣的:(Leetcode刷题之路,leetcode,算法,深度优先)