分治法/動態規劃-最大子序和
阿新 • • 發佈:2019-01-28
給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。
示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6。
分治法【時間複雜度O(NlogN)】
概括來說,就是把陣列分成兩個序列,最大和子序列要麼在左半部分,要麼在右半部分,要麼是橫跨左右部分,也就是說包含左半部分的最後一個元素和右半部分的第一個元素。在求左半部分或者右半部分時又再次將序列拆分,如此迴圈。
* 拆分序列(直到只剩下一個數的序列) ----> 左序列|右序列
* 求左序列最大值
* 求右序列最大值
* 求跨邊界的最大值
* 求以上三個最大值的最大值
* 不斷重複
舉個栗子:
* 假定存在以下序列
* {4 -3 5 -2}
*
* 做拆分
* {4 -3}{5 -2}
* 做拆分
* {4}{-3}
* 做拆分
* {4} 求得當前序列最大和為【4】
* 處理右邊的{-3}
* {-3}小於0,所以返回0,則最大和為【0】
* 還有一種就是橫跨左右序列的-3+4=【1】
* 它們被拆分之前是{4 -3},所以{4 -3}的最大和是max{4,0,1}=【4】
* 處理右邊的
* {5 -2}
* 做拆分
* {5} {-2}
* 做拆分
* {5} 求得序列最大和為【5】
* 處理右邊的{-2}
* {-2}小於0,所以返回0,最大和為【0】
* 還有一種就是橫跨左右序列的-2+5=【3】
* 它們被拆分前是{5 -2},所以{5 -2}最大和是max{5,0,3}=【5】
* {4 -3}{5 -2}處理完畢,最大和分別是【4】【5】
* ------------------------------------------------------------
* 它們被拆分之前是{4 -3 | 5 -2}
* 先說結果:
* 左邊的最大和(4 + (-3)) = 1
* 右邊的最大和5
* 左右相加得【6】
*
* 為什麼是左邊4+(-3)呢?
* 首先以|為邊界向左掃描求最大和
* 因為是從【中間向左】掃,第一個掃到的數是-3
* 再向左掃,是4,4比-3絕對值大吧!
* 要包含4,就必須把-3加上
* 因為要跨越邊界,-3肯定要經過的,才能形成連續的序列。
* 左邊掃描完了,就從【中間向右】掃
* 第一個發現5,掃向下一個,發現是-2,相加起來還不如一個5呢
* 於是就不要-2了。為什麼可以不要?因為這是一個從左往右的序列~
* 而-2在最右邊,當然是可以拋棄的了。
*
* 如果-2 後面還有一個數,比如3的話,是{5 -2 3},這時把三個數加起來是6,比5大,就需要把三個數都包含起來。
* 因為三個數加起來比5大恩。就是這個理。
*
* 此時可以知道{4 -3 | 5 -2},最大和是左邊的1加上右邊的5得到max{4,6,5}=【6】。
採用c++程式碼實現
//求三個數中的最大值
int max3(int a,int b,int c)
{
return a>b?(a>c?a:c):(b>c?b:c);
}
int maxsubarr(vector<int> &nums,int left,int right){
//定義 左右端最大值
int maxleft=0,maxright=0;
//定義左右端邊界值
int leftborder=0,rightborder=0;
//定義左右端邊界最大值
int maxleftborder=0,maxrightborder=0;
//遞迴出口,只剩一個數時
if(left==right)
return nums[left]>0?nums[left]:0;
int mid=(left+right)/2;
//左邊遞迴
maxleft = maxsubarr(nums,left,mid);
//右邊遞迴
maxright = maxsubarr(nums, mid+1, right);
//求左邊屆最大值
for(int i=mid;i>=left;i--){
leftborder+=nums[i];
if(leftborder>maxleftborder)
maxleftborder=leftborder;
}
//求右邊屆最大值
for(int i=mid+1;i<=right;i++){
rightborder+=nums[i];
if(rightborder>maxrightborder)
maxrightborder=rightborder;
}
return max3(maxleft,maxright,maxleftborder+maxrightborder);
}
int maxSubArr(vector<int>nums){
return maxsubarr(nums, 0, (int)nums.size()-1);
}
動態規劃【時間複雜度O(N)】
步驟 1:令狀態 dp[i] 表示以 A[i] 作為末尾的連續序列的最大和(這裡是說 A[i] 必須作為連續序列的末尾)。
步驟 2:做如下考慮:因為 dp[i] 要求是必須以 A[i] 結尾的連續序列,那麼只有兩種情況:
- 這個最大和的連續序列只有一個元素,即以 A[i] 開始,以 A[i] 結尾。
- 這個最大和的連續序列有多個元素,即從前面某處 A[p] 開始 ,一直到 A[i] 結尾
- 對第一種情況,最大和就是 A[i] 本身,對第二種情況,最大和是 dp[i-1]+A[i]。
於是得到狀態轉移方程:
這個式子只和 i 與 i 之前的元素有關,且邊界為 dp[0] = A[0],由此從小到大列舉 i,即可得到整個 dp 陣列。接著輸出 dp[0],dp[1],…,dp[n-1] 中的最大子即為最大連續子序列的和。
採用c++程式碼實現
int maxSubArray(vector<int>& nums) {
int maxsum=0;
int *dp =new int[nums.size()];
dp[0]=nums[0];
maxsum=dp[0];
for(int i=1;i<nums.size();i++){
dp[i]=nums[i]+(dp[i-1]>0?dp[i-1]:0);
maxsum=maxsum>dp[i]?maxsum:dp[i];
}
return maxsum;
}