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]
]

解一:
首先我们会想到暴力求解,也就是用三重for循环依次遍历,在循环前我们需要先对输入数组进行排序,防止重复项(类似[-1,2,-1],[2,-1,-1]这种被认为是重复项)找出所有可能的三元组:

    public List> threeSum1(int[] nums) {
        Set> resultSet = new LinkedHashSet<>();
        Arrays.sort(nums);
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                for (int k = j + 1; k < nums.length; k++) {
                    if (nums[i] + nums[j] + nums[k] == 0) {
                        List list = Arrays.asList(nums[i], nums[j], nums[k]);
                        resultSet.add(list);
                    }
                }
            }
        }
        return new ArrayList<>(resultSet);
    }

执行下,发现超时了:


leetcode12.png

此实现三从for循环,时间复杂度是O(n^3)

解二:
类似于两数之和的升级版,a + b + c = 0,可以理解为-c = a + b,也就是说我们可以进行双重循环i和j,获得-(i+j)的和得到target,判断target是否在缓存中(map吧),因为在map查找key是O(1)时间复杂度,所以可以将解法一的平均时间复杂度降到O(n^2):

    public List> threeSum2(int[] nums) {
        if (nums == null || nums.length < 2) {
            return null;
        }
        Arrays.sort(nums);
        Set> resultList = new LinkedHashSet<>();
        for (int i = 0; i < nums.length - 1; i++) {
            Map cache = new HashMap<>(nums.length);
            for (int j = i + 1; j < nums.length; j++) {
                int target = -nums[i] - nums[j];
                Integer v = cache.get(target);
                if (v != null) {
                    List list = Arrays.asList(nums[i], v, nums[j]);
                    resultList.add(list);
                } else {
                    cache.put(nums[j], nums[j]);
                }
            }
        }
        return new ArrayList<>(resultList);
    }

执行下:


leetcode13.png

终于执行过了,不过这个执行结果,耗时和内存消耗,差了太多了吧~

解法三:
比较巧妙的一种解法是,先将数组排好序(排序的话时间复杂度为O(Nlogn)),然后指定一个节点(pivot),看其右侧数组中有没有和此节点相加为0的,右侧数组具体查找规则为:生成两个指针分别指向该右侧数组的开始和结尾left,right,将其相加看是否为指定节点的负数(三个数相加为0),如果是的话就将其加入结果集中,如果不是,则看下三个数相加的和大于0或者小于0:

  • 如果和大于0的话说明三个数相加超过0了,pivot节点不会动,所以让right节点向左移动一位,继续比较;
  • 如果小的话则说明三个数相加的和小于0了,那么只需要left向后移动一位,然后继续比较;
    public List> threeSum3(int[] nums) {
        if (nums == null || nums.length < 2) {
            return null;
        }
        //先排序,因为后边的算法依赖有序数组
        Arrays.sort(nums);
        List> result = new LinkedList<>();
        for (int pivot = 0; pivot < nums.length - 2; pivot++) {
            if (nums[pivot] > 0) {
                return result;
            }
            //过滤下,如果pivot相同的话,前面已经处理过了,不需要再次处理
            if (pivot != 0 && nums[pivot] == nums[pivot - 1]) {
                continue;
            }
            int left = pivot + 1;
            int right = nums.length - 1;
            while (left < right) {
                int sum = -(nums[left] + nums[right]);
                if (nums[pivot] == sum) {
                    List list = Arrays.asList(nums[pivot], nums[left], nums[right]);
                    result.add(list);
                    //这边处理下重复性问题,因为当left++ == left时,相同的数
                    //据已经被处理过了,所以直接过滤掉即可
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                }
                //如果结果集>0 右指针--
                if (sum < nums[pivot]) {
                    right--;
                } else {
                    left++;
                }
            }
        }
        return result;
    }

再次执行下:


leetcode15.png

很明显的性能提升有没有~~~

你可能感兴趣的:(leetcode-15:三数之和)