LeetCodeOJ刷題之15-16【3Sum(三數和問題)】

本文爲兩個題:【三數和(3Sum)】與【最近三數和(3Sum Closest)】問題

第一部分分析3Sum問題,第二部分分析3Sum Closest問題,因爲兩個問題的思路很像,因此這裏放到一塊兒分析。
其中3Sum問題好像仍是計算機科學領域暫未解決的問題之一,固然,還沒找到更好的解決方案。git

1. 3Sum

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.github

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)

說的意思很簡單,就是:給出一個整型數組,從裏面找三個數 a ,b ,c 而且使 a+b+c=0 。找出全部符合條件的三元組解集。而且解集中不能出現重複的解。數組

Solutions

  • 1 3Sum -- 61~69ms
    • 首先是對數組進行升序排序,而後逐個選擇一個整型,並從剩下的數組元素中選出兩個數字,使這三個數字的和爲0。
    • 選剩下的兩個數字時,第二個數字從第一個數字的下一個數字開始,這是由於,第一個數字及其以前的數字都已進行過查找操做!若是有解,則必定已經找到過了。因此第二個數字從第一個數字的下一個數字開始向後選取;第三個數字從最後一個數字開始向前選取,由於數組已排序,則只要存在,一定可以找到。
    • 按這種思想的代碼以下:
    class Solution {
    public:
        vector<vector<int> > threeSum(vector<int> &num) {
            vector<vector<int> > res;
            if(num.size()<3) return res;//參數檢驗
            sort(num.begin(),num.end()); //先排序
    
            int beg , end , sum;
            vector<int> tmp(3,0);
            for(int i = 0;i<num.size()-2;++i){
                // 去除重複數字的再次查找,避免結果集合中出現重複解
                if(num[i]>0)break;
                if((i>0) && num[i]==num[i-1]) continue;
                beg = i + 1;
                end = num.size()-1;
                while(beg < end){
                    sum=num[beg] + num[end] + num[i];
                    if( sum< 0)        ++beg;
                    else if(sum > 0)   --end;
                    else{
                        tmp[0] = num[i];
                        tmp[1] = num[beg];
                        tmp[2] = num[end];
                        res.push_back(tmp);
                        // 一樣是去除重複數字的再次查找,避免結果集合中出現重複解
                        while(beg<end && num[beg]==tmp[1]) ++beg;
                        while(beg<end && num[end]==tmp[2]) --end;
                        if(beg>=end) break;
                    }
                }
            }
            return res;
        }
    };
    • 測試集進行測試後(博客園這裏圖片好像加載不出來~~):
      測試集運行結果
      https://github.com/bbxytl/LeetCodesOJ/blob/master/Algorithms/15%203Sum/images/pic1.png
    • 在維基百科裏有關於3Sum的說明,同時我又去查找了一下StackOverflow,發現目前只有這種複雜度爲 \(O(N^2)\) 的解決方案,尚未更好的解決方法~~~,也多是我沒有找到。維基百科裏說的算法思想即是上面的方法,並給出了僞代碼:
    sort(S);
     for i=0 to n-3 do
        a = S[i];
        start = i+1;
        end = n-1;
        while (start < end) do
           b = S[start];
           c = S[end];
           if (a+b+c == 0) then
              output a, b, c;
              // Continue search for all triplet combinations summing to zero.
               start = start + 1
               end = end - 1
           else if (a+b+c > 0) then
              end = end - 1;
           else
              start = start + 1;
           end
        end
     end
    未解決的計算機科學問題--算法
    https://github.com/bbxytl/LeetCodesOJ/blob/master/Algorithms/15%203Sum/images/pic2.png

附錄

2. 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).

說的意思很簡單,就是:給出一個整型數組,從裏面找三個數 a ,b ,c 而且使 a+b+c=0 。找出全部符合條件的三元組解集。而且解集中不能出現重複的解。spa

Solutions

  • 1 3Sum Closest -- 18ms
    • 這題的思想和求 [3Sum] 的思想是相似的,所以可使用同思想來求解。
    • 首先是對數組進行升序排序,而後逐個選擇一個整型,並從剩下的數組元素中選出兩個數字,求出這三個數字的和 sum
    • 選剩下的兩個數字時,第二個數字從第一個數字的下一個數字開始,這是由於,第一個數字及其以前的數字都已進行過查找操做!若是有解,則必定已經找到過了。因此第二個數字從第一個數字的下一個數字開始向後選取;第三個數字從最後一個數字開始向前選取,由於數組已排序,則只要存在,一定可以找到。
    • 比較sumtarget的大小,若是相等,則說明三數和與target最近的距離是0,這種狀況是極限狀況,不可能還有比這更近的了,因此可直接返回此sum
    • 若是 sum != target 則判斷當前兩數之間的距離 dt=abs(sum-target)和目前已求得的最短距離 dis 哪一個更小,若是 dt<dis ,則說明出現了一個更接近 target 的解。則記錄下此解的和 res
    • 循環結束後,res 裏記錄的便爲最接近 target 的三數和。
    • 按這種思想的代碼以下:
    class Solution {
    public:
        int threeSumClosest(vector<int> &num, int target) {
            int n=num.size();
            if(n<3)return 0;
            sort(num.begin(),num.end());
            int beg,end,sum=0;
            int dis=INT_MAX;
            int res;
            for(int i=0;i<n-2;++i){
                beg=i+1;
                end=n-1;
                while(beg<end){
                    sum=num[i]+num[beg]+num[end];
                    if(sum==target)return sum;
                    else if(sum<target) ++beg;
                    else --end;
                    int dt=(abs(sum-target));
                    if(dis>dt){
                        dis=dt;
                        res=sum;
                    }
                }
            }
            return res;
        }
    };
  • 2 3Sum Closest -- 15ms
    • 在共享區裏,我發現了這樣一種解法:
    • 咱們剛纔的思想是,先肯定一個數字來定位,而後再遍歷出剩下的兩個數字。這裏的思想正好是反過來的:先肯定兩個數字,再找剩下的一個。
    • 先對數組進行排序,定位首尾兩數。而後求出新的要找的目標數字 newTarget = target - (num[start]+num[end]) ,再使用二分查找查找剩下的那個數字。使用的二分查找返回的數字是找到的比較符合條件的數組中數字的下標。
    • 二分查找方法的時間複雜度是 \(O(logN)\)
    • 根據查出的數字,求三數和 curSum ,比較其與最終要求的target的距離是否小於之間的最小距離 mindiff ,若是小於,則說明找到一個更小的解,給最小距離 mindiff 從新賦值。而後繼續下一輪查找。
    • 由以上分析可知,此算法的時間複雜度是 \(O(NlogN)\) 。要優於方法1 的 \(O(N^2)\)
    • 下面是經我改進後的代碼:
    class Solution {
        // 二分查找
        int findTarget(vector<int> &num, int start, int end, int target) {
            if (start==end) return start;
            if (end-start==1)
                return abs(num[end]-target) > abs(num[start]-target) 
                        ? start : end;
            int mid = (start+end)/2;
            if (num[mid]==target)    return mid;
            else if(num[mid]>target) 
                return findTarget(num, start, mid, target);
            else return findTarget(num, mid, end, target);
        }
    public:
        int threeSumClosest(vector<int> &num, int target) {
            int res=0;
            if(num.size()<=3){
                for(auto v : num) res+=v;
                return res;
            }
            sort(num.begin(), num.end());
            int start = 0;
            int end = int(num.size()-1);
            int mindiff = INT_MAX;
            while (start<end-1) {
                int newTarget = target - (num[start] + num[end]);
                int p = findTarget(num, start+1, end-1, newTarget);
                int curSum = num[start] + num[end] + num[p];
                if (curSum == target) {
                    return target;
                }else if(curSum > target) end--;
                        else start++;
                mindiff = abs(mindiff)>abs(target-curSum) ? target-curSum : mindiff;
            }
            res=target-mindiff;
            return res;
        }
    };

附錄

相關文章
相關標籤/搜索