Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution.
For example, given array S = {-1 2 1 -4}, and target = 1. The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).
我們可以在 2sum問題 的基礎上來解決3sum問題,假設3sum問題的目標是target。每次從數組中選出一個數k,從剩下的數中求目標等於target-k的2sum問題。這里需要注意的是有個小的trick:當我們從數組中選出第i數時,我們只需要求數值中從第i+1個到最后一個范圍內字數組的2sum問題。
我們以選第一個和第二個舉例,假設數組為A[],總共有n個元素A1,A2....An。很顯然,當選出A1時,我們在子數組[A2~An]中求目標位target-A1的2sum問題,我們要證明的是當選出A2時,我們只需要在子數組[A3~An]中計算目標位target-A2的2sum問題,而不是在子數組[A1,A3~An]中,證明如下:
假設在子數組[A1,A3~An]目標位target-A2的2sum問題中,存在A1 + m = target-A2(m為A3~An中的某個數),即A2 + m = target-A1,這剛好是“對於子數組[A3~An],目標位target-A1的2sum問題”的一個解。即我們相當於對滿足3sum的三個數A1+A2+m = target重復計算了。因此為了避免重復計算,在子數組[A1,A3~An]中,可以把A1去掉,再來計算目標是target-A2的2sum問題。
對於本題要求的求最接近解,只需要保存當前解以及當前解和目標的距離,如果新的解更接近,則更新解。算法復雜度為O(n^2);
注意:我們這里是求的和是一個非確定性的數,因此2sum問題的hashtable解法就不適合這里了
1 class Solution { 2 public: 3 int threeSumClosest(vector<int> &num, int target) { 4 int n = num.size(); 5 sort(num.begin(), num.end()); 6 int res, dis = INT_MAX; 7 for(int i = 0; i < n - 2; i++) 8 { 9 int target2 = target - num[i], tmpdis; 10 int tmpres = twoSumClosest(num, i+1, target2); 11 if((tmpdis = abs(tmpres - target2)) < dis) 12 { 13 res = tmpres + num[i]; 14 dis = tmpdis; 15 if(res == target) 16 return res; 17 } 18 } 19 return res; 20 } 21 22 int twoSumClosest(vector<int> &sortedNum, int start, int target) 23 { 24 int head = start, tail = sortedNum.size() - 1; 25 int res, dis = INT_MAX; 26 while(head < tail) 27 { 28 int tmp = sortedNum[head] + sortedNum[tail]; 29 if(tmp < target) 30 { 31 if(target - tmp < dis) 32 { 33 res = tmp; 34 dis = target - tmp; 35 } 36 head++; 37 } 38 else if(tmp > target) 39 { 40 if(tmp - target < dis) 41 { 42 res = tmp; 43 dis = tmp - target; 44 } 45 tail--; 46 } 47 else 48 return target; 49 } 50 return res; 51 } 52 };
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
- Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
- The solution set must not contain duplicate triplets. 本文地址
For example, given array S = {-1 0 1 2 -1 -4}, A solution set is: (-1, 0, 1) (-1, -1, 2)
為了避免重復,對於排序后的數組,當我們枚舉第一個數時,如果遇到重復的就直接跳過;當我們找到一個符合的二元組(第二個數和第三個數)時,也分別對第二個數和第三個數去重。具體見代碼注釋。代碼中的兩個函數也可以合並成一個。
1 class Solution { 2 public: 3 vector<vector<int> > threeSum(vector<int> &num) { 4 int n = num.size(); 5 sort(num.begin(), num.end()); 6 vector<vector<int> > res; 7 for(int i = 0; i < n-2; i++) 8 { 9 if(i > 0 && num[i] == num[i-1])continue;//重復的元素不用計算 10 int target2 = 0 - num[i]; 11 twoSum(num, i+1, target2, res); 12 } 13 return res; 14 } 15 void twoSum(vector<int> &sortedNum, int start, int target, vector<vector<int> >&res) 16 { 17 int head = start, tail = sortedNum.size() - 1; 18 while(head < tail) 19 { 20 int tmp = sortedNum[head] + sortedNum[tail]; 21 if(tmp < target) 22 head++; 23 else if(tmp > target) 24 tail--; 25 else 26 { ; 27 res.push_back(vector<int>{sortedNum[start-1], sortedNum[head], sortedNum[tail]}); 28 29 //為了防止出現重復的二元組,使結果等於target 30 int k = head+1; 31 while(k < tail && sortedNum[k] == sortedNum[head])k++; 32 head = k; 33 34 k = tail-1; 35 while(k > head && sortedNum[k] == sortedNum[tail])k--; 36 tail = k; 37 } 38 } 39 } 40 };
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)
算法1:我們可以仿照3sum的解決方法。這里枚舉第一個和第二個數,然后對余下數的求2sum,算法復雜度為O(n^3),去重方法和上一題類似
1 class Solution { 2 public: 3 vector<vector<int> > fourSum(vector<int> &num, int target) { 4 int n = num.size(); 5 vector<vector<int> > res; 6 sort(num.begin(), num.end()); 7 for(int i = 0; i < n-3; i++) 8 { 9 if(i > 0 && num[i] == num[i-1])continue;//防止第一個元素重復 10 for(int j = i+1; j < n-2; j++) 11 { 12 if(j > i+1 && num[j] == num[j-1])continue;//防止第二個元素重復 13 int target2 = target - num[i] - num[j]; 14 int head = j+1, tail = n-1; 15 while(head < tail) 16 { 17 int tmp = num[head] + num[tail]; 18 if(tmp > target2) 19 tail--; 20 else if(tmp < target2) 21 head++; 22 else 23 { 24 res.push_back(vector<int>{num[i], num[j], num[head], num[tail]}); 25 //為了防止出現重復的二元組,使結果等於target2 26 int k = head+1; 27 while(k < tail && num[k] == num[head])k++; 28 head = k; 29 30 k = tail-1; 31 while(k > head && num[k] == num[tail])k--; 32 tail = k; 33 } 34 } 35 } 36 } 37 return res; 38 } 39 };
算法2:O(n^2)的算法,和前面相當,都是先對數組排序。我們先枚舉出所有二個數的和存放在哈希map中,其中map的key對應的是二個數的和,因為多對元素求和可能是相同的值,故哈希map的value是一個鏈表(下面的代碼中用數組代替),鏈表每個節點存的是這兩個數在數組的下標;這個預處理的時間復雜度是O(n^2)。接着和算法1類似,枚舉第一個和第二個元素,假設分別為v1,v2, 然后在哈希map中查找和為target-v1-v2的所有二元對(在對應的鏈表中),查找的時間為O(1),為了保證不重復計算,我們只保留兩個數下標都大於V2的二元對(其實我們在前面3sum問題中所求得的三個數在排序后的數組中下標都是遞增的),即時是這樣也有可能重復:比如排好序后數組為-9 -4 -2 0 2 4 4,target = 0,當第一個和第二個元素分別是-4,-2時,我們要得到和為0-(-2)-(-4) = 6的二元對,這樣的二元對有兩個,都是(2,4),且他們在數組中的下標都大於-4和-2,如果都加入結果,則(-4,-2,2,4)會出現兩次,因此在加入二元對時,要判斷是否和已經加入的二元對重復(由於過早二元對之前數組已經排過序,所以兩個元素都相同的二元對可以保證在鏈表中是相鄰的,鏈表不會出現(2,4)->(1,5)->(2,4)的情況,因此只要判斷新加入的二元對和上一個加入的二元對是否重復即可),因為同一個鏈表中的二元對兩個元素的和都是相同的,因此只要二元對的一個元素不同,則這個二元對就不同。我們可以認為哈希map中key對應的鏈表長度為常數,那么算法總的復雜度為O(n^2)
1 class Solution { 2 public: 3 vector<vector<int> > fourSum(vector<int> &num, int target) { 4 int n = num.size(); 5 vector<vector<int> > res; 6 unordered_map<int, vector<pair<int, int> > >pairs; 7 pairs.reserve(n*n); 8 sort(num.begin(), num.end()); 9 10 for(int i = 0; i < n; i++) 11 for(int j = i+1 ; j < n; j++) 12 pairs[num[i]+num[j]].push_back(make_pair(i,j)); 13 14 for(int i = 0; i < n - 3; i++) 15 { 16 if(i != 0 && num[i] == num[i-1])continue;//防止第一個元素重復 17 for(int j = i+1; j < n - 2; j++) 18 { 19 if(j != i+1 && num[j] == num[j-1])continue;//防止第二個元素重復 20 if(pairs.find(target - num[i] - num[j]) != pairs.end()) 21 { 22 vector<pair<int, int>> &sum2 = pairs[target - num[i] - num[j]]; 23 bool isFirstPush = true; 24 for(int k = 0; k < sum2.size(); k++) 25 { 26 if(sum2[k].first <= j)continue;//保證所求的四元組的數組下標是遞增的 27 if(isFirstPush || (res.back())[2] != num[sum2[k].first]) 28 { 29 res.push_back(vector<int>{num[i], num[j], num[sum2[k].first], num[sum2[k].second]}); 30 isFirstPush = false; 31 } 32 } 33 } 34 } 35 } 36 37 return res; 38 } 39 };
對於k-sum問題,我們可以不斷的轉化為k-1 sum, k-2 sum 直到2sum;也可以像4sum問題的hashmap解法一樣,分成若干個2sum問題。可以參看這篇文章:
k sum problem (k 個數的求和問題)
【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/p/3649607.html