【LeetCode】三数之和

声明:这只是一个题解,题目是LeetCode来的。著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目:

15. 三数之和

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

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

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

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

题解:

思路1:

三元组不可以重复,那我们可以先不考虑是不是重复,而是找到以后都扔到Set里边去。
找的时候可以先取第一个数a,然后取第二个数b,计算需要的第三个数的值c,然后在数组里找有没有这个值。可以用Map来存放被访问过的值,这样就不用重复地去查询数组。

 public List<List<Integer>> threeSum(int[] nums){
        int count = 0;//计数,与题目无关
        if(nums == null || nums.length < 3) return new ArrayList<>();
        Set<List<Integer>> set = new HashSet<>();
        Arrays.sort(nums);//排序
        for(int i = 0; i < nums.length; i++){
            int first = nums[i];
            Map<Integer,Integer> map = new HashMap<>();
            for(int j = i + 1; j < nums.length; j++){
                count++;
                int needed = 0 - first - nums[j];//找到俩,需要找第三个
                if(map.containsKey(needed)){//找到第三个
                    List<Integer> list = new ArrayList<>();
                    list.add(first);
                    list.add(nums[j]);
                    list.add(needed);
                    set.add(list);
                }
                if(!map.containsKey(nums[j]))//把第二个加入备选
                    map.put(nums[j],j);
            }
        }
        List<List<Integer>> answer = new ArrayList<>(set);
        System.out.println("count :" + count);
        return answer;
    }

 

执行情况:

输入:
{-1,0,1,2,-1,-4}
输出:
count :15
-1 2 -1 
-1 1 0 

能否优化呢?我们可以看到在这里循环进行的次数是15次。也就是说每取一个first元素,都要遍历他后面的所有元素。

1.实际上我们通过升序排序后,就能发现,如果我们前3个数字之和就大于0的话,那再往后取也没意义了,也不会再有解了,直接结束循环。

2.如果我们取当前最小的first,和最大以及次大的元素,他们的和如果小于0的话,说明此时的first也不合适,太小了,直接跳过它去找下一个比他大一点的first。

3.一开始我们在用Set去重,但我们发现排序之后的数组为{-4,-1-1,0,1,2},当我们取绿色的-1做first的时候,找到的3元组为{-1,0,1},{-1-1,2},下一个first取蓝色的-1时,找到的3元组为{-1,0,1},也就是说,当相邻的两个元素相同时,我们先取的那个元素(绿色-1)得到的解,与后一个元素能得到的解相同,于是我们在遇到-1时,判断它上一个元素是否和他相同,相同就跳过-1这样我们不会得到重复的解。

 public List<List<Integer>> threeSum(int[] nums){
        int count = 0;
        if(nums == null || nums.length < 3) return new ArrayList<>();
//        Set<List<Integer>> set = new HashSet<>();//优化后不再需要Set了
        List<List<Integer>> three = new ArrayList<>();
        Arrays.sort(nums);//排序
        for(int i = 0; i < nums.length - 2; i++){
            int first = nums[i];

            //优化1:最小的3个相加>0 就不用考虑后面的了,没有解了。
            if(first + nums[i + 1]+ nums[i + 2] > 0) break;
            //最小的加上俩最大的,都小于0,这个小的不考虑了。
            if(first + nums[nums.length - 1] + nums[nums.length - 2] < 0)continue;
            //这个数字与前一个重复,它在上一轮被考虑过了,这次就不考虑它。
            if(i > 0 && first == nums[i - 1])continue;

            Map<Integer,Integer> map = new HashMap<>();
            for(int j = i + 1; j < nums.length; j++){
                count++;
                int needed = 0 - first - nums[j];//找到俩,需要找第三个
                if(map.containsKey(needed)){//找到第三个
                    List<Integer> list = new ArrayList<>();
                    list.add(first);
                    list.add(nums[j]);
                    list.add(needed);
                    three.add(list);
                }
                if(!map.containsKey(nums[j]))//把第二个加入备选
                    map.put(nums[j],j);
            }
        }
        System.out.println("count :" + count);
        return three;
    }

执行情况:

输出:
count :4
-1 1 0 
-1 2 -1 

 可能你发现了,上面这段代码其实还有点问题,就是当我们遇到{0,0,0,0}这样的输入时,还是会得到两组{0,0,0},也就是说,当我们选第一个0和第二个0,我们可以和第3个0得到一个三元组,然后我们会去查看第4个0,发现它依旧能和第一个0以及第二个0进行组合,所以说,还是会得到重复的元组,所以上面的第三部优化没有真正做到去重(可以用Set大法:),对于出现多个重复的元素,我们应该怎么去重呢?

我们可以用双端指针来做这件事,对于{000000}这样的情况,首先我们选红0做first,然后在后面的0里变找剩下两个元素,设置两个指针low指向黄0,high指向紫0,我们比较他们3个的和,如果<0,我们把low++,如果大于0,我们把high--,如果等于0,我们就找到了我们要的三元组,于是把它加入结果列表中,此时我们去判断,low的右边有没有和它一样的元素,有的话low++右移,同时,我们去看high的左边有没有和它相同的元素,有的话,high--,直到low>high或者遇到不相同的元素,结束本次操作。

public List<List<Integer>> threeSum2(int[] nums){
        int count = 0;
        if(nums == null || nums.length < 3) return new ArrayList<>();
        List<List<Integer>> three = new ArrayList<>();
        Arrays.sort(nums);//排序
        for(int i = 0; i < nums.length - 2; i++){
            //优化1:最小的3个相加>0 就不用考虑后面的了,没有解了。
            if(nums[i] + nums[i + 1]+ nums[i + 2] > 0) break;
            //最小的加上俩最大的,都小于0,这个小的不考虑了。
            if(nums[i] + nums[nums.length - 1] + nums[nums.length - 2] < 0)continue;
            //这个数字与前一个重复,它在上一轮被考虑过了,这次就不考虑它。
            if(i > 0 && nums[i] == nums[i - 1])continue;

            int low = i + 1, high = nums.length - 1;
            while(low < high){
                count++;
                int sum = nums[i] + nums[low] + nums[high];
                if(sum == 0){
                    three.add(Arrays.asList(nums[i],nums[low],nums[high]));
                    while(low < high && nums[low] == nums[low + 1]) low ++;
                    while(low < high && nums[high] == nums[high - 1]) high --;
                    low ++;
                    high --;
                }else if(sum < 0) low ++;
                else high --;
            }
        }
        System.out.println("count:" + count);
        return three;
    }

执行结果:

输入:
{0,0,0,0,1,1,2,2}
输出:
count:5
0 0 0 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值