1. Question
给定整型数组和target,找数组中的所有和为target的四个数,将其按值升序排列输出,输出结果不包含重复数对。
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: 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)
2. Solution
2.1 O(n3)
- 数组排序
- i:第i个数是否在某个四数对中,i初值为len-1;
- 在。在剩下的数中找满足条件的3Sum
- 不在。删除该数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Solution { //use threeSum, O(n3) public List<List<Integer>> fourSum( int[]nums, int target ){ List<List<Integer>> res = new ArrayList<List<Integer>>(); int len = nums.length; if( len<4 ) return res; Arrays.sort( nums ); Set<List<Integer>> testUnique = new HashSet<List<Integer>>(); for( int i=len-1; i>=3; i-- ){ for( int j=0; j<=i-3; j++ ){ int twoSum = target- nums[i] - nums[j]; for( int k=j+1, p=i-1; k<p; ){ int nowTwo = nums[k] + nums[p]; if( nowTwo < twoSum ) k++; else if( nowTwo > twoSum ) p--; else{ List<Integer> newFour = new ArrayList<Integer>(); newFour.add( nums[j] ); newFour.add( nums[k] ); newFour.add( nums[p] ); newFour.add( nums[i] ); if( testUnique.add(newFour) ) res.add( newFour ); k++; p--; } } } } return res; } }
上述方法可以改进,即对其中某些选项剪枝,效率提高很多,但最坏情况依然是O(n3)
- a, b, c, d:四数对
- maxDWhenAIncrease: 当a, b, c为最小可能值时,如果a+b+c+maxDWhenAIncrease > target,则maxDWhenAIncrease--
- maxDWhenBIncrease:当a, b, c为最小可能值时,如果a+b+c+maxDWhenBIncrease > target,则maxDWhenBIncrease--
- 剪枝:
- 重复数:当前a/b和上一次a/b相同,无需重复计算;
- a太小或太大:
- 太小:a和当前b, c, d的最大可能值之和 < target
- 太大:a和当前b, c, d的最小可能值之和 > target
- b太小或太大(此时a已确定):
- 太小:a+b和当前c, d的最大可能值之和 < target
- 太大:a+b和当前c, d的最小可能值之和 > target
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//improved solution using threeSum. worst time O(n3) public List<List<Integer>> fourSum( int[] nums, int target ){ List<List<Integer>> res = new ArrayList<List<Integer>>(); int len = nums.length; if( len<4 ) return res; int maxDWhenAIncrease = len-1; int maxDWhenBIncrease; Arrays.sort( nums ); for( int i=0; i<=maxDWhenAIncrease-3; i++ ){ //remove duplicates and prune when a is too small if( ( i>0 && nums[i]==nums[i-1] ) || ( nums[i] + nums[maxDWhenAIncrease-2] + nums[maxDWhenAIncrease-1] + nums[maxDWhenAIncrease] < target ) ) continue; //stop when a is too big if( nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target ) break; //update maxDWhenAIncrease & maxDWhenBIncrease while( nums[i] + nums[i+1] + nums[i+2] + nums[maxDWhenAIncrease] > target ) maxDWhenAIncrease--; maxDWhenBIncrease = maxDWhenAIncrease; for( int j=i+1; j <= maxDWhenBIncrease-2; j++ ){ //remove duplicates and prune when b is too small if( ( j>i+1 && nums[j] == nums[j-1] ) || ( nums[i] + nums[j] + nums[maxDWhenBIncrease-1] + nums[maxDWhenBIncrease] < target) ) continue; //stop when b is too big if( nums[i] + nums[j] + nums[j+1] + nums[j+2] > target ) break; //update maxDWhenBIncrease while( nums[i] + nums[j] +nums[j+1] + nums[maxDWhenBIncrease] > target ) maxDWhenBIncrease--; int remainSum = target - nums[i] - nums[j]; //use two pointers to find the remain two numbers for( int m=j+1,n=maxDWhenBIncrease; m<n; ){ int nowSum = nums[m] + nums[n]; if( nowSum < remainSum ) while( (++m) < n && nums[m] == nums[m-1] ); else if( nowSum > remainSum ) while( (--n) > m && nums[n] == nums[n+1] ); else{ List<Integer> newFour = new ArrayList<Integer>(); newFour.add( nums[i] ); newFour.add( nums[j] ); newFour.add( nums[m] ); newFour.add( nums[n] ); res.add(newFour); while( (++m) < n && nums[m] == nums[m-1] ); while( (--n) > m && nums[n] == nums[n+1] ); } } } } return res; }
2.2 O(n4)
利用2Sum,先求出所有双数对和,然后遍历和求符合要求的数对(2sum)。
占用内存过多,效果不好。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//improved solution of O(n2) time public List<List<Integer>> fourSum3( int[] nums, int target ){ List<List<Integer>> res = new ArrayList<List<Integer>>(); int len = nums.length; if( len<4 ) return res; Arrays.sort(nums); HashMap<Integer, List<List<Integer>>> twoSums = new HashMap<Integer, List<List<Integer>>>(); //calculate all the two sum pairs for( int i=0; i<len-1; i++ ) for( int j=i+1; j<len; j++ ){ List<Integer> newNowSumCouple = new ArrayList<Integer>(); newNowSumCouple.add( i ); newNowSumCouple.add( j ); List<List<Integer>> newNowSumList = new ArrayList<List<Integer>>(); int nowSum = nums[i] + nums[j]; if( twoSums.containsKey( nowSum ) ){ newNowSumList = twoSums.get( nowSum ); newNowSumList.add( newNowSumCouple ); twoSums.replace( nowSum, newNowSumList); } else{ newNowSumList.add( newNowSumCouple ); twoSums.put( nowSum, newNowSumList); } } //traverse the twoSums map to find the satisfied quadruples for( Map.Entry<Integer, List<List<Integer>>> twoSum: twoSums.entrySet() ){ int sum1 = twoSum.getKey(); int sum2 = target - sum1; if( !twoSums.containsKey(sum2) ) continue; List<List<Integer>> sumList1 = twoSum.getValue(); List<List<Integer>> sumList2 = twoSums.get( sum2 ); for( int i=0; i<sumList1.size(); i++ ){ int a = nums[sumList1.get(i).get(0)]; int b = nums[sumList1.get(i).get(1)]; //remove duplicates in sumList1 if( i==0 || a != nums[sumList1.get(i-1).get(0)]) for( int j=sumList2.size()-1; j>=0; j-- ){ int c = nums[sumList2.get(j).get(0)]; int d = nums[sumList2.get(j).get(1)]; //remove duplicates in sumList2 and only keep the quadruples that a<=b<=c<=d (check through the index since the array is sorted) if( sumList1.get(i).get(1) < sumList2.get(j).get(0) && (j==sumList2.size()-1 || c != nums[sumList2.get(j+1).get(0)]) ){ List<Integer> newFour = new ArrayList<Integer>(); newFour.add(a); newFour.add(b); newFour.add(c); newFour.add(d); res.add(newFour); } } } } return res; }