三数之和

题目描述

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有
满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum

思路:首先将数组从小到大排序。假设三个数为L,M,R。M从1到length-1。L为最左边的数,R为最右边的数,M为中间的数。如果L+M+R>0则证明R偏大,R–。否则为L偏小,L++。如果三者相加等于0,则存入List。直到LM||RM。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
     List<List<Integer>> ans = new ArrayList<>();
        Arrays.sort(nums); // 排序
        int len = nums.length;
        if (len < 3) return ans;//没有答案
        for (int i = 1; i < len - 1; i++) {
            int L = 0;
            int R = len - 1;
            while (L < i && R > i) {
                int sum = nums[L] + nums[i] + nums[R];
                if (sum == 0) {
                    ans.add(Arrays.asList(nums[i], nums[L], nums[R]));
                    L++;
                    R--;
                } else if (sum < 0) L++;//L偏小
                else R--;//R偏大
            }
        }
        return ans;
    }
}

写完了,美滋滋地贴上去,结果发现。
在这里插入图片描述
原来是有重复的值未处理啊。emm…这可如何是好。
既然有重复的,那就试图把它去了呗。
1.如果M的前一个值和当前相等,跳过当前值
2.如果L的后一个值和当前相等,跳过当前值
3.如果R的后一个值和当前相等,跳过当前值
好的,代码来了:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
       List<List<Integer>> ans = new ArrayList<>();
        Arrays.sort(nums); // 排序
        int len = nums.length;
        if (len < 3) return ans;
        for (int i = 1; i < len - 1; i++) {
            if(nums[i-1]>0) break;//第一个数大于0,后面肯定不会有答案
            if (i > 1 && nums[i - 1] == nums[i]) continue;//去重
            int L = 0;
            int R = len - 1;
            while (L < i && R > i) {
                int sum = nums[L] + nums[i] + nums[R];
                if (sum == 0) {
                    ans.add(Arrays.asList(nums[i], nums[L], nums[R]));
                    while (L < R && nums[L] == nums[L + 1]) L++; // 去重
                    while (L < R && nums[R] == nums[R - 1]) R--; //去重
                    L++;
                    R--;
                } else if (sum < 0) L++;
                else R--;
            }
        }
        return ans;
    }
}

快快乐乐的提交,本以为完全没问题了,可是结果。
在这里插入图片描述
分析用例,发现{-4,-1,-1,0,1,2},当-1和-1想当时,M会跳过后一个-1。然而L用这个-1,正好可以组成一组解。M每后移一次L与R的取值返回一定会变,所以不能单纯的判定M相等就跳过M,因为M会决定L与R的取值,跳过M后,可能会错过某些答案。而L与R再怎么移动也不会改变其他两个变量的取值。所以L与R可以跳过。那么有什么办法可以安全地跳过M呢。
在跳过之前再加个判断,如果M的值已经有了一组解,那么这个 M就可以跳过了(因为已经有了解,所以没有必要在求解了)。否则,不可以跳过(没有解,并且L与R的取值范围已经变化,所以可能会有解)。
这样就需要再些一个判断是否已经有解的方法,遍历ans List。

看了题解,发现了一个更好的方法:

首先对数组进行排序,排序后固定一个数nums[i],再使用左右指针指向nums[i]后面的两端,数字分别为nums[L]和nums[R],计算三个数的和sum判断是否满足为 0,满足则添加进结果集
如果nums[i]大于 0,则三数之和必然无法等于 0,结束循环
如果nums[i] = nums[i-1],则说明该数字重复,会导致结果重复,所以应该跳过
当sum = 00 时,nums[L] = nums[L+1]则会导致结果重复,应该跳过,L++
当sum =0时,nums[R] = nums[R-1]则会导致结果重复,应该跳过,R–

class Solution {
    public static List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList();
        Arrays.sort(nums); // 排序
        int len = nums.length;
        if(nums == null || len < 3) return ans;
        for (int i = 0; i < len ; i++) {
            if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
            if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
            int L = i+1;
            int R = len-1;
            while(L < R){
                int sum = nums[i] + nums[L] + nums[R];
                if(sum == 0){
                    ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
                    while (L<R && nums[L] == nums[L+1]) L++; // 去重
                    while (L<R && nums[R] == nums[R-1]) R--; // 去重
                    L++;
                    R--;
                }
                else if (sum < 0) L++;
                else if (sum > 0) R--;
            }
        }        
        return ans;
    }
}

作者:guanpengchn
链接:https://leetcode-cn.com/problems/two-sum/solution/hua-jie-suan-fa-15-san-shu-zhi-he-by-guanpengchn/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

将M放在头位,L放在M后一位,R放在最右。这样无论M如果跳过都不会影响结果,因为跳过的那个值,L在上一轮已经求解过了

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值