LeetCode:3Sum, 3Sum Closest, 4Sum


3Sum Closest

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

 


 

3Sum

Given an array S of n integers, are there elements abc 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 };

 


 

 

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)

算法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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM