java三数之和

题目描述

给你一个整数数组 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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

欢迎关注个人公众号【好好学技术】交流学习

解法

1.暴力法

老规矩,先试试暴力法然后在考虑能否优化。
思路很简单就是三次for循环,获取结果。

    /**
     * 遍历三次
     * 时间复杂度O(n^3)
     *
     * @param nums 数组
     * @return 返回所有和为 0 且不重复的三元组。
     */
    private static List<List<Integer>> threeSum1(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        int length = nums.length;
        //三重for循环
        //三个且不重复的元素,所以i的最大值为 length - 2
        for (int i = 0; i < length - 2; i++) {
            for (int j = i + 1; j < length - 1; j++) {
                for (int k = j + 1; k < length; k++) {
                    //判断三数相加之和等于0
                    if (nums[i] + nums[j] + nums[k] == 0) {
                        result.add(Arrays.asList(nums[i], nums[j], nums[k]));
                    }
                }
            }
        }

        //结果去重
        // result 结果 [[-1, 0, 1], [-1, 2, -1], [0, 1, -1]]
        //[-1, 0, 1] 和 [0, 1, -1] 是重复的,需要去重
        return distinctList(result);
    }

    /**
     * list 去重
     */
    private static List<List<Integer>> distinctList(List<List<Integer>> result) {
        List<List<Integer>> distinctList = new ArrayList<>();
        for (List<Integer> list : result) {
            //因为数组长度都为3,所以不用比较长度。只需排个序,然后使用contains方法判断即可
            //contains会遍历每个元素比较
            //list.sort(null) 如果指定的比较器为空,则此列表中的所有元素都必须实现 Comparable 接口,并且应使用元素的自然顺序。
            list.sort(null);
            System.out.println("list排序后为:" + list);
            if (!distinctList.contains(list)) {
                distinctList.add(list);
            }
        }
        return distinctList;
    }

这种方法虽然简单,但是缺点也是明显的,for循环嵌套两层for循环,时间复杂度为O(n^3)。

2.双指针法

双指针其实就是使用左右指针法,借鉴的是分治思想。
在数组的头尾分别放置左右指针。通过指针移动来进行搜索,指针相遇时,代表搜索结束。

下面来说说具体的思路

1.对数组进行升序排序。
2.开始遍历数组nums,如果nums[i]已经大于0,直接跳出循环,如果当前数和上一个数一样,直接continue(结果也不用去重了)
3.以当前位置i为三数中的第一个解,i+1位置为左指针(最小数)lp,数组最后一个数字为右指针(最大数)rp。
4.接下来就是对 nums[i] + nums[lp] + nums[rp] 的和sum进行判断了。
5.如果sum等于0,就找到了当前位置i的一组解。左指针右移,右指针左移,继续寻找下一组解。
6.如果sum小于0,说明左右指针中的较小数应该增大,即左指针右移。
7.如果sum大于0,说明左右指针中的较大数减少,即右指针左移。

列个表格说明一下运行过程
nums = [-1,0,1,2,-1,-4]
排序后 nums = [-4,-1,-1,0,1,2]

双指针.png

好了,接下来贴上代码


    /**
     * 双指针法
     * 时间复杂度O(n^2)
     *
     * @param nums 数组
     * @return 返回所有和为 0 且不重复的三元组。
     */
    private static List<List<Integer>> threeSum2(int[] nums) {
        //结果数组
        List<List<Integer>> result = new ArrayList<>();
        //1.先排序,默认升序
        Arrays.sort(nums);
        int length = nums.length;
        //2.遍历数组
        for (int i = 0; i < length; i++) {
            //如果当前数(最小数)已经大于0,直接退出循环
            if (nums[i] > 0) {
                break;
            }
            //如果当前数已经出现过,直接跳过,也不用去重了
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            //3.以i之后的最小数,定义为左指针
            int lp = i + 1;
            //3.以最后一个数(最大数),定义为右指针
            int rp = length - 1;
            //只要左右指针不重叠,就继续移动指针
            while (lp < rp) {
                //4.当前数和左右指针的和
                int sum = nums[i] + nums[lp] + nums[rp];
                //判断sum,与0做大小对比
                if (sum == 0) {
                    //5.找到了一组解
                    result.add(Arrays.asList(nums[i], nums[lp], nums[rp]));
                    //直接移动左右指针
                    lp++;
                    rp--;
                    //如果左指针移动之后元素相同,直接跳过
                    while (lp < rp && nums[lp] == nums[lp - 1]) {
                        lp++;
                    }
                    //如果右指针移动之后元素相同,直接跳过
                    while (lp < rp && nums[rp] == nums[rp + 1]) {
                        rp--;
                    }
                } else if (sum < 0) {
                    //6.小于0,左右指针中的较小数增大,即左指针右移
                    lp++;
                } else {
                    //7.sum > 0, 左右指针中的较大数减少,即右指针左移
                    rp--;
                }
            }
        }
        return result;
    }

时间复杂度方面来思考一下。 首先是外层for循环,里层其实就是左右指针的移动,所以时间复杂度为O(n^2)。

空间复杂度没有用到任何附加的数据结构,所以空间复杂度为O(1)。

源码地址

https://github.com/fandf/algorithm/blob/master/src/main/java/com/fandf/algorithm/ThreeSum.java

最后

千里之,行始于足下

如果大家有更好的解法或者疑问,欢迎留言一起交流学习。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值