【算法】leetcode15. 三数之和

描述

给你一个整数数组 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,即当前取出的是第几个元素,然后令 leftindex+1right 为数组的最后一位即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!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0neXiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值