Idiot-maker

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

https://oj.leetcode.com/problems/3sum/

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

Note:

  • Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
  • The solution set must not contain duplicate triplets.

 

    For example, given array S = {-1 0 1 2 -1 -4},

    A solution set is:
    (-1, 0, 1)
    (-1, -1, 2)

解题思路:

三个加数,首先想到排序,快排花费时间O(n*logn)。然后想到从头和尾各确定一个数字,在他们中间用二分法找第三个加数。时间复杂度O(n*logn + n^2 * logn),超时。

于是想到,由之前的two sum那道题,已知一个加数,求另一个加数,很容易想到用hashmap,在O(n)的时间内求解。这样就可以省去找第三个数字的对数时间。将时间复杂度减少到O(n^2)。

public class Solution {
    public List<List<Integer>> threeSum(int[] num) {
        Arrays.sort(num);
        
        HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
        
        for(int i = 0; i < num.length; i++){
            hashMap.put(num[i], i);
        }
        
        Set<Integer> leftSet = new HashSet<Integer>();
        Set<Integer> rightSet = new HashSet<Integer>();
        
        List<List<Integer>> resultList = new ArrayList<List<Integer>>();
        for(int i = 0; i < num.length / 2; i++){
            for(int j = num.length - 1; j > i; j--){
                int left = 0 - num[i] - num[j];
                //用二分法查找第三个元素,超时
                // int start = i + 1;
                // int end = j - 1;
                // while(start <= end){
                //     int mid = (start + end) / 2;
                //     if(num[mid] > left){
                //         end = mid - 1;
                //     }
                //     if(num[mid] < left){
                //         start = mid + 1;
                //     }
                //     if(num[mid] == left){
                //         List<Integer> list = new ArrayList<Integer>();
                //         list.add(num[i]);
                //         list.add(num[mid]);
                //         list.add(num[j]);
                //         resultList.add(list);
                //         break;
                //     }
                // }
                
                //用hashmap查找第三个元素
                if(hashMap.containsKey(left) && hashMap.get(left) != i && hashMap.get(left) != j 
                && !leftSet.contains(num[i]) && !rightSet.contains(num[j])){
                    List<Integer> list = new ArrayList<Integer>();
                    list.add(num[i]);
                    list.add(left);
                    list.add(num[j]);
                    resultList.add(list);
                    leftSet.add(num[i]);
                    rightSet.add(num[j]);
                    // break;
                }
            }
        }
        return resultList;
    }
}

但是这个解法会遇到一个很复杂的问题,HashMap无法确定重复元素的位置。于是重新找思路。

下面是一个可行的解法。和上面的解法思路类似,但是不同。也是先排序,从头至尾遍历数组,只确定一个数num[i],然后找剩下的两个数。那么这两个数一定在[i + 1, num.length - 1]。如果这三个数字和==0,那么就是解答了,如果>0,证明end--,如果<0,那么start++。这很容易理解。

需要注意的就是去除重复答案的处理过程。第一个遍历加数,如果当前处理数字和前一个相等,也就是num[i] == num[i - 1],就可以跳过了。因为num[i]已经在[i + 1, num.length - 1]的区间内求解结束了。num[i + 1]和num[i]相等,在后面更小的一个区间内,即使有解,也一定是重复的。

注意这里一定是判断num[i] == num[i - 1],而不是num[i] == num[i + 1],因为要和前面已经求过解的元素比较,而不是后面为求解的元素。同理对于start和end的处理。

public class Solution {
    public List<List<Integer>> threeSum(int[] num) {
        Arrays.sort(num);
        
        List<List<Integer>> resultList = new ArrayList<List<Integer>>();
        
        for(int i = 0; i < num.length - 2; i++){
            //必须与前一个数字相比,和已经处理过的数字比,如果重复,就跳过
            if(i > 0 && num[i] == num[i - 1]){
                continue;
            }
            int start = i + 1;
            int end = num.length - 1;
            
            while(start < end){
                if(start > i + 1 && num[start] == num[start - 1]){
                    start++;
                    continue;
                }
                if(end < num.length - 1 && num[end] == num[end + 1]){
                    end--;
                    continue;
                }
                if(num[i] + num[start] + num[end] == 0){
                    List list = new ArrayList();
                    list.add(num[i]);
                    list.add(num[start]);
                    list.add(num[end]);
                    resultList.add(list);
                    start++;
                    end--;
                }else if(num[i] + num[start] + num[end] > 0){
                    end--;
                }else if(num[i] + num[start] + num[end] < 0){
                    start++;
                }
            }
        }
        return resultList;
    }
}

 由上面的解法我们可以知道,对于一个已经排序的数组,找两个sum确定的数字,是一个在O(n)的时间内求解的。

posted on 2015-02-10 16:57  NickyYe  阅读(406)  评论(0编辑  收藏  举报