标签:
https://oj.leetcode.com/problems/4sum/
Given an array S of n integers, are there elements a, b, c, 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:
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)的时间内求解。
标签:
原文地址:http://www.cnblogs.com/NickyYe/p/4286156.html