【leetcode】-最大子序和(leetcode53)

最大子序和

給定一個整數數組 nums ,找到一個具備最大和的連續子數組(子數組最少包含一個元素),返回其最大和。ios

  • 示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4],

輸出: 6

解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。

分治法

  • 思路:數組

    將原數組分紅左右兩部分,元素數都爲n/2,令最大子數組的下標爲i,j則i和j的狀況分爲三種:
    cors

    1. i,j都屬於原數組的左半側,即low<=i<=j<=mid
    2. i,j都屬於原數組的有半側,即mid<i<=j<=high
    3. i,j跨越mid點,即low<=i<=mid<j<=high

    則根據上述的三種狀況創建分治策略,分別求解左側部分的最大子數組、右側部分的最大子數組和跨越mid的最大子數組,而後比較上述三個最大子數組取最大值,即爲給定數組的最大子數組curl

  • 僞碼:優化

    //求解跨越mid的最大子數組
    //因爲i,j跨越mid,則左側的末尾元素必定是mid,右側的起始元素必定是mid+1
    FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)
        //跨越mid左側部分的最大值
        left-sum=MIN //初始值爲最小值
        sum=0
        max-left//存儲下標
        for i=mid to low
            sum=A[i]+sum
            if sum>left-sum
                max-left=i
                left-sum=sum
    
        //跨越mid右側部分的最大值
        right-sum=MIN
        sum=0
        max-right
        for j=mid+1 to high
            sum=A[j]+sum
            if sum>right-sum
                max-right=j
                right-sum=sum
        return(max-left,max-right,left-sum+right-sum)
    
    //求數組A的最大子數組
    FIND-MAXIMUM-SUBARRAY(A,low,high)
        if low==high
            return(low,high,A[low])//只有一個元素的狀況,返回當前元素的位置和值
        else
            mid=(low+high)/2
            (left-low,left-high,left-sum)=FIND-MAXIMUM-SUBARRAY(A,low,mid)
            (right-low,right-high,right-sum)=FIND-MAXIMUM-SUBARRAY(A,mid+1,high)
            (corss-low,cross-high,cross-sum)=FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)
            if left-sum>right-sum and left-sum>corss-sum
                return (left-low,left-high,left-sum)
            else if right-sum>left-sum and right-sum>corss-sum
                return (right-low,right-high,right-sum)
            else
                return (cross-low,cross-high,cross-sum)
  • 時間複雜度分析:求解跨越mid的最大子數組部分時間複雜度爲Θ(n),分解部分時間複雜度爲常數,則T(n)=2T(n/2)+Θ(n),即T(n)=Θ(nlgn)
  • 源碼實現:url

    #include <iostream>
    #include <tuple>
    #include <limits.h>
    using namespace std;
    typedef tuple<int,int,long long> resultType;
    resultType FindMaxCrossSubarray
        (int *A,int low,int mid,int high)
    {
        int left_max =mid;
        long long leftMaxSum = LLONG_MIN;
        long long sum=0;
        for(int i = mid;i>=low;--i)
        {
            sum=sum+A[i];
            if(sum>leftMaxSum)
            {
                left_max = i;
                leftMaxSum = sum;
            }
        }
    
        int right_max = mid+1;
        long long rightMaxSum=LLONG_MIN;
        sum = 0;
        for(int j=mid+1;j<=high;++j)
        {
            sum=sum+A[j];
            if(sum>rightMaxSum)
            {
                right_max = j;
                rightMaxSum = sum;
            }
        }
        return {left_max,right_max,leftMaxSum+rightMaxSum};
    }
    
    resultType FindMaxSubArray(int *A,int low,int high)
    {
        if(low == high)
        {
            return {low,high,A[low]};
        }
        else
        {
            int mid = (low+high)/2;
            resultType leftResult = FindMaxSubArray(A,low,mid);
            resultType rightResult = FindMaxSubArray(A,mid+1,high);
            resultType crossResult = FindMaxCrossSubarray(A,low,mid,high);
            if(get<2>(leftResult)>get<2>(rightResult)&&get<2>(leftResult)>get<2>(crossResult))
            {
                return leftResult;
            }
            else if(get<2>(rightResult)>get<2>(leftResult)&&get<2>(rightResult)>get<2>(crossResult))
            {
                return rightResult;
            }
            else
            {
                return crossResult;
            }  
        } 
    }
    
    int main()
    {
        int test[9]{-2,1,-3,4,-1,2,1,-5,4};
        resultType result = FindMaxSubArray(test,0,sizeof(test)/sizeof(int)-1);
        for(int i=get<0>(result);i<=get<1>(result);i++)
        {
            cout<<test[i]<<ends;
        }
        cout<<endl<<get<2>(result);
        system("pause");
        return 0;
    }

分治法優化

  • 思路爲改變計算跨越mid的最大子數組的方法。[l...r]這個區間的最大子數組可能爲[l...m]的最大子數組和[m+1...r]的最大子數組,或包含m和m+1的最大子數組(即跨越mid)。統計四個量,分別爲:spa

    1. lsum:以l開頭的最大子數組
    2. rsum:以r結尾的最大子數組
    3. isum:當前全部元素的和
    4. msum:當前最大子數組
  • 則在合併時根據左、右兩側的統計量更新合併後的統計量,最終獲得的msum即爲最大子數組。更新規則爲code

    1. 合併後的lsum爲左側的子串的lsum或左側子串的isum加上右側子串的lsum,取較大值爲合併後的lsum
    2. 合併後的rsum爲右側子串的rsum或者右側子串的isum加上左側子串的rsum,取較大值爲合併後的rsum
    3. 合併後的isum爲左右兩側isum之和
    4. 合併後的msum爲左側的msum或右側的msum或左側的rsum加上右側lsum三者取最大值爲新的msum
  • 僞碼實現:get

    FIND-MAX-SUBARRAY(A,low,high)
        if low==high
            return (A[low],A[low],A[low],A[low])//四個數分別問lsum,rsum,isum,msum
        else
            mid = (low+high)/2
            (l_lsum,l_rsum,l_isum,l_msum)=FIND-MAX-SUBARRAY(A,low,mid)
            (r_lsum,r_rsum,r_isum,r_msum)=FIND-MAX-SUBARRAY(A,mid+1,high)
            lsum=l_lsum>(l_isum+r_lsum) ? l_lsum :l_isum+r_lsum
            rsum=r_rsum>(r_isum+l_rsum) ? r_rsum:r_isum+l_rsum
            isum=l_isum+r_isum
            if l_msum>r_msum and l_msum>(l_rsum+r_lsum)
                return (lsum,rsum,isum,l_msum)
            if r_msum>l_msum and r_msum>(l_rsum+r_lsum)
                return (lsum,rsum,isum,r_msum)
            else
                return (lsum,rsum,isum,l_rsum+r_lsum)
  • 時間複雜度分析:分解部分時間複雜度爲常數,合併部分時間複雜度也爲常數。則T(n)=2T(n/2)+Θ(1),即T(n)=Θ(lgn)
  • 源碼實現:如下源碼只求出的最大子數組的和,沒有保存最大子數組對應的下標源碼

    #include <iostream>
    #include <tuple>
    
    using namespace std;
    
    using resultType = tuple<long long,long long,long long,long long>;
    
    resultType FindMaxSubArray(int *A,int low,int high)
    {
        if(low==high)
        {
            return {A[low],A[low],A[low],A[low]};
        }
        else
        {
            int mid = (low+high)/2;
            resultType lResult =FindMaxSubArray(A,low,mid);
            resultType rResult =FindMaxSubArray(A,mid+1,high);
            int l_lsum = get<0>(lResult),
            l_rsum = get<1>(lResult),
            l_isum = get<2>(lResult),
            l_msum = get<3>(lResult);
            int r_lsum = get<0>(rResult),
            r_rsum = get<1>(rResult),
            r_isum = get<2>(rResult),
            r_msum = get<3>(rResult);
            int lsum = l_lsum > (l_isum+r_lsum) ? l_lsum :l_isum+r_lsum;
            int rsum = r_rsum > (r_isum+l_rsum) ? r_rsum:r_isum+l_rsum;
            int isum = l_isum+r_isum;
            if(l_msum>r_msum && l_msum>(l_rsum+r_lsum))
            {
                return {lsum,rsum,isum,l_msum};
            }      
            else if(r_msum>l_msum && r_msum>(l_rsum+r_lsum))
            {
                return {lsum,rsum,isum,r_msum};
            }
            else
            {
                return {lsum,rsum,isum,l_rsum+r_lsum};
            }        
        }
    }
    int GetMaxSubArrayValue(int *A,int low,int high)
    {
        resultType result = FindMaxSubArray(A,low,high);
        return get<3>(result);
    }
    
    int main()
    {
        int test[9]{-2,1,-3,4,-1,2,1,-5,4};
        int result = GetMaxSubArrayValue(test,0,sizeof(test)/sizeof(int)-1);
        cout<<endl<<result;
        system("pause");
        return 0;
    }

最大子數組問題貪心法求解

  • 思路若是已知數組A[1...j]的最大組數組,則A[1...j+1]的最大子數組爲A[1...j]的子數組,或者是以j+1爲末尾元素的子數組。以j+1爲結尾的子數組有兩種,一種是隻包含j+1位置上的元素,另一種爲j+1前的子序列加上j+1。若是j+1前的子序列和爲正數,則包含以前的子序列的數組元素和大於只包含j+1位置元素的數組。按照這種思路從左到右遍歷數組便可獲得最大子數組
  • 僞碼:

    FIND-MAX-SUBARRAY(A,low,high)
        cursum=MIN//用於保存當前求和的結果
        curleft=0//用於保存當前的計算的可能的最大子數組的左邊界
        maxsum=MIN//用於保存已遍歷部分的最大和
        maxleft=0//保存已遍歷部分的最大和對應的左邊界
        maxright=0//保存已遍歷部分的最大和對應的右邊界
        for i=low to high
            if cursum<0
                cursum=A[i]
                curleft=i
            else
                cursum=cursum+A[i]
            if cursum>maxsum
                maxsum=cursum
                maxleft=curleft
                maxright=i
        return (maxsum,maxleft,maxright)
  • 時間複雜度分析:只遍歷一遍,時間複雜度爲Θ(n)
  • 源碼實現:

    #include <iostream>
    #include <tuple>
    #include <limits.h>
    using namespace std;
    
    using ResultType=tuple<long long,int,int>;
    ResultType FindMaxSubArray(int *A,int low,int high)
    {
        long long cursum =  LLONG_MIN;
        int curleft = 0;
        long long maxsum = LLONG_MIN;
        int maxleft = 0;
        int maxright = 0;
        for(int n=low;n<=high;++n)
        {
            if(cursum<0)
            {
                cursum=A[n];
                curleft=n;
            }
            else
            {
                cursum+=A[n];
            }
            if(cursum>maxsum)
            {
                maxsum = cursum;
                maxleft = curleft;
                maxright = n;
            }
        }
        return {maxsum,maxleft,maxright};
    }
    int main()
    {
        int test[9]{-2,1,-3,4,-1,2,1,-5,4};
        ResultType result = FindMaxSubArray(test,0,sizeof(test)/sizeof(int)-1);
        for(int i=get<1>(result);i<=get<2>(result);i++)
        {
            cout<<test[i]<<ends;
        }
        cout<<endl<<get<0>(result);
        system("pause");
        return 0;
    }
相關文章
相關標籤/搜索