1. 程式人生 > >LeetCode#53暨最大連續子序列和問題

LeetCode#53暨最大連續子序列和問題

這是一道很有意思的演算法題。說它有意思包含了幾個方面的內容:首先,它的直觀上的求解顯而易見、非常容易,但是它的優化求解直到上世紀八十年代才被發現;其次,很多演算法書籍(例如《演算法導論》、《程式設計珠璣》,以及Mark Allen Weiss的演算法書等)都會討論它,可見它已經是演算法設計的典型教學案例了;最後,它也是各種IT公司筆試面試時常常考察的一道經典演算法題目(LeetCode網站上它的題目編號是53)。

來看一下LeetCode網站上關於這道題目的描述:


一、解決方法(一):Brute Force

暴搜的方法最straightforward,我們不做解釋。僅給出實現程式碼如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //int length = nums.size();
        
        int sum = 0;
        int max = -2147483648;
        
        for(vector<int>::iterator it1  = nums.begin(); it1 != nums.end(); it1++){
            sum = *it1;
            if(max < sum)
                max = sum;
            
            for (vector<int>::iterator it2 = it1+1; it2 != nums.end(); it2++) {
                sum += *it2;
                
                if(max < sum)
                    max = sum;
            }
        }
        
        return max;
    }
};

但是,BF的複雜度是 O(n2),如果你將上述答案提交到LeetCode,則會顯示超時!

二、解決方法(二):  Divide to Conquer

演算法導論上有討論這個方法。它的基本認識是,如果把陣列分成左右兩段,那麼加和最大的連續子序列,要麼出現在陣列的左半部分,要麼出現在陣列的右半部分,要麼出現在中間,即從左半部分和右半部分相鄰的地方各區一段。所以可以用分治法來求解,具體實現時需要藉助遞迴。例項程式碼如下:

class Solution {
public:
    
    int maxSubSumRec(vector<int>& nums, int left, int right){
        
        if(left >= right){
            return nums[left];
        }
        
        int i,center;
        center = (left + right)/2;
        int lmax = maxSubSumRec(nums, left, center - 1);
        int rmax = maxSubSumRec(nums, center +1, right);
        int mmax = nums[center], t = mmax;
        
        for(i = center - 1; i >= left; --i){
            t += nums[i];
            mmax = max(mmax, t);
        }
        
        t = mmax;
        for(i = center + 1; i <=right; ++i){
            t += nums[i];
            mmax = max(mmax, t);
        }
        
        return max(mmax, max(lmax, rmax));
    }
    
    
    int maxSubArray(vector<int>& nums) {
        
        int length = int(nums.size()-1);
        return maxSubSumRec(nums, 0, length);
    }
};

演算法的複雜度是O(nlogn)。

三、解決方法(三):  Dynamic Programming

這個演算法又稱為Kadane演算法,它是又美國卡耐基梅隆大學的教授Kadane發明的一種用於求解最大連續子序列和問題的最優演算法。對於一個長度為n的陣列A而言,從A[0] 到 A[j] 是一個子陣列(j<n),那麼以A[j]結尾的子陣列之最大和,要麼是 A[j], 要麼是 max(A[i]~A[j-1])+A[j] ,其中0 ≤ i ≤ j-1。這就是該演算法設計的出發點。

如果你需要了解Kadane演算法的更多細節,參考文獻【1】是講解該演算法的一個非常好的視訊。下面我直接給出基於該演算法實現的程式程式碼:

class Solution {
public:
    
    int maxSubArray(vector<int>& nums) {
        
        vector<int>::iterator it  = nums.begin();
        int maxSum = *it;
        int theSum = *it;
        
        for(it = it+1 ; it != nums.end(); it++){
            theSum = max(theSum + *it, *it);
            
            if(theSum > maxSum)
                maxSum = theSum;
        }
        
        return maxSum;
    }
};
上述實現的複雜度是O(n)。

最後給出一個用來測試上述函式執行的主程式程式碼:

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, const char * argv[]) {
    
    int n[] = {6,85,46,-74,26,30,25,-15,-22};
    
    vector<int> a(n,n+9);
    
    Solution sol;
    cout<<sol.maxSubArray(a)<<endl;
    
    return 0;
}


參考文獻

【1】https://www.youtube.com/watch?v=86CQq3pKSUw

(本文完)

本部落格中已經討論過的LeetCode題目列表