3Sum

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)的时间内求解的。

你可能感兴趣的:(SUM)