码迷,mamicode.com
首页 > 其他好文 > 详细

Leetcode之15. 3Sum (medium)

时间:2018-10-02 00:49:28      阅读:241      评论:0      收藏:0      [点我收藏+]

标签:暴力破解   list   目的   strong   lis   索引   break   .so   步骤   

15. 3Sum (medium)

描述

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

分析

首先是最容易想到的暴力破解,通过三重遍历数组nums,依次确定nums[i]nums[j]nums[k]并计算三元素之和是否为0。这是最粗暴的解法,但是存在去重的问题,如[-1, 0, 1]和[0, 1, -1]这种情况。

其次,这个题目作为2Sum的进阶题目,很容易联想到将3Sum转化为求target值为0 - nums[i],并在数组剩余元素中找出两个元素之和为target的2Sum问题。但是同样存在去重问题。

关于去重,由于是一个List

最后,以上两种思路都存在去重问题,问题需要的是找出数组中三个元素之和为0的所有组合。去重过程很明显是和结果无关,但是却非常麻烦,因此要优化算法就要着眼于移除去重这个步骤。

在暴力破解的时候就该意识到边界问题。

在做第一层循环时可以这样写:for(int i = 0; i < nums.length - 2; i++)。即第一层循环的结束条件是nums.length - 2,并不需要到nums.length

同样第二层循环时:for(int j = i + 1; j < nums.length - 1; j++)。开始索引不需要从0开始,可以直接从i + 1开始,而结束为nums.length - 1

第三层:for(int k = j + 1; k < nums.length; k++)

可以看到在暴力破解的时候,我们已经有意识地通过边界条件过滤掉一些情况,进行了初步优化。注意到数组本身是无序的,所以在确定元素的时候难以界定当前遍历元素是否已经被选中过。如果数组是有序的,那么三重遍历的时候就可以有意识地跳过重复元素。到这里已经对暴力破解的解题思路进行了优化,但是三重遍历无疑是导致时间复杂度为O(N^3),这么高的时间复杂度肯定是要被抛弃的。那么该如何继续优化呢?

尝试优化思路二。首先使用排序解决去重问题。遍历排序后的数组,固定第一个元素为nums[i],接下来在索引位i + 1nums.length之间找出两个元素nums[j]nums[k],二者之和为0 - nums[i]。固然这可以做遍历两次达到目的,相信基本上2Sum都是这样完成的。但是针对一个有序数组,夹逼法可以将这个过程的时间复杂度降为O(N)。因此,使用夹逼法找出剩余两个元素。ps,别忘了同时对2Sum使用夹逼法进行优化。

代码

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new LinkedList<List<Integer>>();
    if (nums == null || nums.length < 3) {
        return result;
    }
    // 对数组排序
    Arrays.sort(nums);
    //固定第一个元素nums[i]
    for (int i = 0; i < nums.length - 2; i++) {
        //默认是从小到大的排序,所以当nums[i]大于0的时候,就可以结束
        if (nums[i] > 0) {
            break;
        }
        //nums[i - 1] != nums[i]执行了去重,注意这里在理解的时候要意识到此时操作的数组已经是有序数组
        if (i == 0 || nums[i - 1] != nums[i]) {
            //使用加逼法
            int j = i + 1;
            int k = nums.length - 1;
            while (j < k) {
                int sum = nums[i] + nums[j] + nums[k];
                if (sum == 0) {
                    result.add(Arrays.asList(nums[i], nums[j], nums[k]));
                } 
                if (sum <= 0) {
                    while (j < k && nums[j] == nums[++j]);
                }
                if(sum >= 0){
                    while (j < k && nums[k] == nums[--k]);
                }
            }
        }
    }
    return result;
}

上面的代码是优化之后的代码,对于理解加逼的过程有点不便,下面是加逼的原始写法:

while (j < k) {
    int target = 0 - nums[i];
    if(target == (nums[j] + nums[k])){
        result.add(Arrays.asList(nums[i], nums[j], nums[k]));
        j++;
        while(nums[j] == nums[j - 1] && j < k){ //去重,注意这是一个有序数组
            j++;
        }
        k--;
        while(nums[k] == nums[k + 1] && j < k){ //去重,注意这是一个有序数组
            k--;
        }
    }else if(target < (nums[j] + nums[k])){
        k--;
        while(nums[k] == nums[k + 1] && j < k){ 
            k--;
        }
    }else if(target > (nums[j] + nums[k])){
        j++;
        while(nums[j] == nums[j - 1] && j < k){ 
            j++;
        }
    }  
}

Leetcode之15. 3Sum (medium)

标签:暴力破解   list   目的   strong   lis   索引   break   .so   步骤   

原文地址:https://www.cnblogs.com/wxiaoqi/p/9736143.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!