4Sum

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里。

这么做其实是把这个4Sum的问题,化解为两个2Sum问题。由于2Sum问题是可以在O(n)时间内求解的,4Sum问题可以在O(n^2)的时间内求解。

但是,这时会遇到一个很难解决的问题,无法判断坐标是否重复。极端情况下,如果num数组的所有元素都相同,他在sumMap里其实是只有一个key,而value的组合却有n^2种之多。因为是4sum,要判断这所有N^2个pair对的index组合在已有答案中不存在,光这个问题的时间复杂度就要达到O(n^4)。

有人说,不应考虑index是否重复,而是实际的num[i]是否重复,因为题目要求的是解唯一。比如,对于{0,0,0,0,0,0,0}这样的数组。这样看似可以无需用上面O(n^4)的时间去比较,但也必须声明一种新的类型后来又想到,这里可以将这个结果定义为另一种类,比如quadruplet,并且重写他的equals()方法(重写equals()方法就必须重写hashCode()方法),用来判断他们实际上是否相等。至少,要生成一个triplet的类,因为四个数字重复和三个数字重复实际上是一样的,总和一定,剩下一个必然相同。

为什么要定义一个新的类型,因为传统的set只适合一个int的去重,这里除非范围缩小到两个数字的去重,否则无法实现。这个解法比较繁琐,而且花费另外的内存,这里就没有再去实现。下面是一个尝试的代码。

事实上,这么做的逼近解,我感觉只能有O(n^2*2logn),当然比O(n^3)有改进。因为问题化简为两个2sum的问题后,问题就是对pair求2sum。因为原来2sum的解法是,首先花费nlogn的时间排序,然后用hashmap在O(1)的时间内求sum。那么这里,n=n^2。于是时间复杂度就是O(n^2*2logn)。

于是,和上面一样,你花费O(n^2)的时间,获得所有两两可能的pair,然后将它们放入一个list中,对其进行排序。这里就需要为这个pair类,实现Comparable接口,重写compareTo()方法。以及,equals()和hashCode()方法。

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);

            }

        }

        

        Iterator iter = sumMap.entrySet().iterator(); 

        while (iter.hasNext()) { 

            Map.Entry entry = (Map.Entry) iter.next(); 

            Integer sum = entry.getKey(); 

            List<int[]> pairList = entry.getValue(); 

            

            int remaining = target - sum;

            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);

            }

        }

        

        // 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;

    }

}

我到最后也没找到严格意义上的O(n^2)解法,主要是去重。有人坚持说有,参见下面几个链接吧。

https://oj.leetcode.com/discuss/21401/is-there-really-a-o-n-2-solution

https://oj.leetcode.com/discuss/18953/share-my-ac-o-n2-python-solution

http://cs.stackexchange.com/questions/2973/generalised-3sum-k-sum-problem

http://tech-wonderland.net/blog/summary-of-ksum-problems.html

http://blog.csdn.net/linhuanmars/article/details/24826871

你可能感兴趣的:(SUM)