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

4Sum

时间:2015-02-11 23:09:05      阅读:212      评论:0      收藏:0      [点我收藏+]

标签:

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

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

Note:

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

 

    For example, given array S = {1 0 -1 0 -2 2}, and target = 0.

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

解题思路:

根据上面3Sum的解法,很容易解决这题。取第i个数,于是剩下的问题就变成了在[i + 1, length -1]这个区间内的3sum问题。代码如下。

public class Solution {
    public List<List<Integer>> fourSum(int[] num, int target) {
        List<List<Integer>> quadruplets = new ArrayList<List<Integer>>();
        if(num.length < 4){
            return quadruplets;
        }
        Arrays.sort(num);
        for(int i = 0; i < num.length - 3; i++){
            if(i > 0 && num[i] == num[i - 1]){
                continue;
            }
            for(int j = i + 1; j < num.length - 2; j++){
                if(j > i + 1 && num[j] == num[j - 1]){
                    continue;
                }
                int start = j + 1;
                int end = num.length - 1;
                while(start < end){
                    if(start > j + 1 && num[start] == num[start - 1]){
                        start++;
                        continue;
                    }
                    if(end < num.length - 1 && num[end] == num[end + 1]){
                        end--;
                        continue;
                    }
                    int sum = num[i] + num[j] + num[start] + num[end];
                    if(sum == target){
                        List list = new ArrayList();
                        list.add(num[i]);
                        list.add(num[j]);
                        list.add(num[start]);
                        list.add(num[end]);
                        quadruplets.add(list);
                        start++;
                        end--;
                    }else if(sum < target){
                        start++;
                    }else if(sum > target){
                        end--;
                    }
                }
            }
        }
        return quadruplets;
    }
}

这么做的时间复杂度为O(n^3)。网上查出有接近O(n^2)的解法,于是开始尝试写。

大概意思就是,首先对原数组中任意两两求和,记录这每个pair的和以及坐标情况。一个二维数组用来记录pair的和,一个hashmap,key为pair的和,value是一个list,list内的成员是一个个的数组,数组的length为2,就是和为这个key的两两下标。这个sumMap的泛型格式为Map<Integer, List<int[]>>。于是需要O(n^2)的时间。

首先想到遍历这个hashmap,对于一个key,也就是存在的一种可能的和,看看target-sum,在不在sumMap中,不在当然放弃。在的话,就取出value,分析它的pair情况,并且加入到结果的list里。但是,这时会遇到一个很难解决的问题,无法判断坐标是否重复。极端情况下,如果num数组的所有元素都相同,他在sumMap里其实是只有一个key,而value的组合却有n^2种之多。因为是4sum,要判断结果的四个index都不能重复,光这个问题的时间复杂度就要达到O(n^4)。

后来又想到,这里可以将这个结果定义为另一种类,比如quadruplet,并且重写他的equals方法。太过复杂,这里就没有再去实现。

public class Solution {
    public List<List<Integer>> fourSum(int[] num, int target) {
        List<List<Integer>> quadruplets = new ArrayList<List<Integer>>();
        if(num.length < 4){
            return quadruplets;
        }
        //排序,便于后面从小到大排序
        Arrays.sort(num);
        
        int[][] sumArray = new int[num.length][num.length];
        Map<Integer, List<int[]>> sumMap = new HashMap<Integer, List<int[]>>();
        
        for(int i = 0; i < num.length; i++){
            for(int j = 0; j < i; j++){
                sumArray[i][j] = num[i] + num[j];
                List<int[]> list;
                if(sumMap.get(num[i] + num[j]) == null){
                    list = new ArrayList<int[]>();
                }else{
                    list = sumMap.get(num[i] + num[j]);
                }
                list.add(new int[]{i, j});
                sumMap.put(num[i] + num[j], list);
            }
        }
        
        for(int i = 0; i < num.length; i++){
            for(int j = 0; j < i; j++){
                int remaining = target - sumArray[i][j];
                if(sumMap.get(remaining) == null){
                    continue;
                }else{
                    List<int[]> list1 = sumMap.get(remaining);
                    for(int[] pair : list1){
                        if(pair[0] != i && pair[0] != j && pair[1] != i && pair[1] != j){
                            List list = new ArrayList();
                            list.add(num[i]);
                            list.add(num[j]);
                            list.add(num[pair[0]]);
                            list.add(num[pair[1]]);
                            quadruplets.add(list);
                        }
                    }
                    sumMap.remove(remaining);
                }
            }
        }
        
        return quadruplets;
    }
}

辗转反侧,参考了网上的考虑思路,写出了一个近似于O(n^2)的解。思路和上面类似,还是首先花费O(n^2)的时间,生成一个个pair对,和他们的和,以及这样的一个sumMap。区别就是,下面不遍历这个sumMap,去对每一个sum,看看另一个被加数是否存在。而是,首先仍然双重遍历num数组,先确定两个元素,剩下的和,不用最上面的夹逼方法,而是从sumMap中去找,这样只需花费O(1)的时间就可以了。但是!判断这个剩下的pair是否重复,还是依赖于这个sumMap的这个key下的value,也就是list的长度的。也就是下面代码中的 for(int[] pair : list1)。

考虑大多数情况,这个list的长度并不会达到n,所以最差情况才会达到O(n^3)的复杂度,比较理想的这里应该还是仅仅花费O(1)而已。

另一个需要主要的一点,后面的双重循环,内层j是从i+1开始,就是代表和是取得sum[0][1]这样的形式。那么前面的sumArray就一定要取在坐下角,也就是前面的j也要从i+1开始,否则不对应的话,sumArray都没有初始化过,就都是0了。

如果前面的sumArray初始化了右上角,后面的循环也只能在右上角里。

前提是,你要理解,sumArray[i][j] == sumArray[j][i],所以仅仅要取以对角线划分的左下角或右上角即可。

public class Solution {
    public List<List<Integer>> fourSum(int[] num, int target) {
        List<List<Integer>> quadruplets = new ArrayList<List<Integer>>();
        if(num.length < 4){
            return quadruplets;
        }
        //排序,便于后面从小到大排序
        Arrays.sort(num);
        
        int[][] sumArray = new int[num.length][num.length];
        Map<Integer, List<int[]>> sumMap = new HashMap<Integer, List<int[]>>();
        Set<Integer> set = new HashSet<Integer>();
        
        //首先生成一个二维数组sumArray,包num数组每两位的和
        //同时生成一个sumMap,key是每两位的和,value是每对的index
        for(int i = 0; i < num.length; i++){
            for(int j = i + 1; j < num.length; j++){
                sumArray[i][j] = num[i] + num[j];
                List<int[]> list;
                if(sumMap.get(num[i] + num[j]) == null){
                    list = new ArrayList<int[]>();
                }else{
                    list = sumMap.get(num[i] + num[j]);
                }
                list.add(new int[]{i, j});
                sumMap.put(num[i] + num[j], list);
            }
        }
        
        for(int i = 0; i < num.length - 3; i++){
            //防止重复
            if(i > 0 && num[i] == num[i - 1]){
                continue;
            }
            for(int j = i + 1; j < num.length - 2; j++){
                //防止重复
                if(j > i + 1 && num[j] == num[j - 1]){
                    continue;
                }
                int remaining = target - sumArray[i][j];
                if(sumMap.get(remaining) == null){
                    continue;
                }else{
                    //如果有和为期望值的pair,遍历这些所有的pair
                    List<int[]> list1 = sumMap.get(remaining);
                    //这个set很重要,用来判断这个remaining值下的pair是否重复
                    set.clear();
                    for(int[] pair : list1){
                        //他们的index必须大于前两个数字的index,并且第一个index没有在set里出现过
                        if(pair[0] > i && pair[0] > j && pair[1] > i && pair[1] > j && !set.contains(num[pair[0]])){
                            set.add(num[pair[0]]);
                            List list = new ArrayList();
                            list.add(num[i]);
                            list.add(num[j]);
                            //从小到大排列
                            if(pair[0] <= pair[1]){
                                list.add(num[pair[0]]);
                                list.add(num[pair[1]]);
                            }else {
                                list.add(num[pair[1]]);
                                list.add(num[pair[0]]);
                            }
                            quadruplets.add(list);
                        }
                    }
                    // sumMap.remove(remaining);
                }
            }
        }
        
        return quadruplets;
    }
}

通过这个例子,我们可以看到,4Sum的问题,可以化解为两个2Sum问题。由于2Sum问题是可以在O(n)时间内求解的,4Sum问题可以在接近于O(n^2)的时间内求解。

4Sum

标签:

原文地址:http://www.cnblogs.com/NickyYe/p/4286156.html

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