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

转载于:https://www.cnblogs.com/NickyYe/p/4286156.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值