問題的提出:給定有n個整數(可能為負整數)組成的序列a1,a2,...,an,求該序列連續的子段和的最大值。如果該序列的所有元素都是負整數時定義其最大子段和為0。
例如,當(a1,a2,a3,a4,a5)=(-5,11,-4,13,-4-2)時,最大子段和為11+(-4)+13=20。
解法一:窮舉法,即把所有可能情況一一枚舉
窮舉法是最直接的想法,把所有的情況列出來,再進行挑選。
同樣是窮舉法,下面兩個寫法優劣就不一樣。有的人可能還會增加空間開銷,使用乙個陣列來儲存結果。
1)使用三層迴圈
下面的演算法是這樣的:(使用字典序的方式)從序列a的第乙個開始,算a[0]的和,算a[0]~a[1]的和,算a[0]~a[2]的和
……算a[0]~a[n-1]的和,然後算a[1]的和,算a[1]~a[2]的和,算a[1]~a[3]的和,一直算到a[n-2]~a[n-1]的和、
算a[n-1]的和,在每次計算a[i]~a[j]的和後,都要和當前最大子段和sum比較,若發現更大的,就更新sum的值。
前兩層迴圈就是完成字典序窮舉,而第三層迴圈是計算a[i]~a[j]的和。
//begin,end分別記錄最大子段和的開始和結尾位置的下標,下標從0開始
//a是待求陣列,n是序列長度
int maxsum(int a,int n,int &begin,int &end)
} return sum;
}
這演算法很清晰,就是挨個列舉,如果發現有比sum更大的值,就更新sum。但是重複做了很多任務作,導致時間複雜度為o(n^3),每一次計算a[i]~a[j]的和都要從a[i]一直累加至a[j],其實我們是可以先儲存a[i]~a[j-1]的和至乙個變數temsum,那麼a[i]~a[j]的和就等於temsum+a[j],這就是下面兩層迴圈的寫法
2)使用兩層迴圈
int maxsum(int a,int n,int &begin,int &end)
} }return sum;
}
可以看到,儲存了a[i]~a[j-1]和的結果後,就可以省去一層迴圈,時間複雜度也降為o(n^2)。我們在寫程式時要根據題目的要求而選擇比較省時省空間的寫法,這也需要多練習。
解法二:利用分治策略
先要明白分治策略基本思想是把問題規模分解為多個小規模問題,遞迴地解這些子問題,然後將各子問題的解合併得到原問題的解。一般採用二分法逐步分解(注意,很多演算法都用到遞迴,當然這很耗空間)。分治法解題的一般步驟:
(1)分解,將要解決的問題劃分成若干規模較小的同類問題;
(2)求解,當子問題劃分得足夠小時,用較簡單的方法解決;
(3)合併,按原問題的要求,將子問題的解逐層合併構成原問題的解。
本題目總的分治思想是:
如果將所給的序列a[1:n]分為長度相等的兩段子序列a[1:n/2]和a[n/2+1:n],分別求出這兩段子序列的最大子段和,則總序列的最大子段和有三種情況:1)與前段相同。2)與後段相同。3)跨前後兩段。
(我想這解法比較難理解的地方是3)跨前後兩段的情況(理解時可以簡單列舉乙個序列按**執行)。這裡注意一下,跨前後兩段是指乙個連續的子串行跨越前後兩段,而不是前後兩段最大欄位和的簡單相加)
具體的分治做法是這樣的:先把a[1:n]分成a[1:n/2]和a[n/2+1:n],分別求出兩段子序列的最大子段和,而在求a[1:n/2]的最大子段和時,又把a[1:n/2]分成a[1:(n/2)/2]和a[(n/2)/2+1:n/2]兩個子串行,照這樣一直分,直到把每個子串行都只有乙個或兩個數未知,當子串行只有乙個數時,它的最大子段和要麼是自身或為0,而子串行有兩個數時,其最大子段和要麼為前乙個數,要麼為後乙個數,要麼為兩個數的和,或者為0(當兩個數都為負數時),當返回子串行的最大子段和時,子串行的最大子段和乙個數就代表了乙個子串行(這點很重要),那麼後面每次處理的子串行都是只有或者兩個數(因為子串行的最大子段和代表了這個序列)。可以舉個例子照著程式執行一下,幫助理解。
//left是做端點下標,right是右端點下標
int maxsubsum(int a,int left,int right)
//從中間向右擴充套件。中間往右的第乙個必然包含在內
int rs=0;int rights=0;
for(i=++center;i<=right;i++)
sum=ls+rs;//sum儲存跨前後兩段情況的最大子段和
//求跨前後兩段的情況完成
if(sum
初學者要理解這個演算法需要好好去舉個例子。解法四的思想或許會對你理解有些幫助。
這個演算法的時間複雜度為o(nlogn),分治演算法在這主要是作為想法學習用,並不是這道題的最佳演算法。
解法三:動態規劃
本題目總的動態規劃思想是這樣的:
已知前n個數的最大子段和,那麼前n+1個數的最大欄位和有兩種情況,一是包含前面的結果,二是不包含。
具體做法是這樣的,序列a有n個數,我們就要做n次決策,從第乙個數開始(下標從0開始),假設已經做好了前i個數的決策,並把做第i個數的最大子段和的結果儲存到了tem(注意,前i個數的最大子段和sum和第i個數決策的子段和tem是不一樣的,前者sum可能不包含第i個數,但第i個數決策的子段和tem一定包含tem,sum是當前最大子段的和,而tem是包含第i個數的子段和,並想辦法使tem的值盡可能的大),當做第i+1個數的決策時,要做的工作就只是判斷包含第i+1個數的子段和是否要把tem的值包進來,如果tem>0,就包括,否則不包括。
(再看一下總的想法)假設前n個數的最大子段和是tem,在決策前n+1個數的最大子段和時,判斷tem的值,如果tem>0,那麼前n+1個數的最大子段和為tem加上第n+1個數,否則就是第n+1個數自己。這裡記住,你所求的是連續的幾個數的和。**比較簡單:
//begin和end分別表示最大子段和的開始和結束位置的下標,下標從0開始。
int maxsum(int a,int n,int &begin,int &end)
if(tem>sum)
} return sum;
}
只需一次遍歷,時間複雜度為o(n),動態規劃裡有一項很重要的內容就是儲存各階段的狀態,有人會增加乙個陣列儲存狀態,但寫程式可以根據題目要求做些改變,像這道題就只需要儲存前乙個狀態就行。
解法四:
最大子段的左右兩個數字必定為正數,最左邊數字的左鄰是負數,最右邊數字的右鄰是負數。假設a[i]~a[j]是最大子段和序列的乙個子串行,則從a[i-1]逐個往左加,這個和如果在加到a[k]時變成乙個正數,那就說明左端點i可以延伸到k,可以使這個子段的和更大一些,右邊也同理擴充套件。我們要做的就是找到最大欄位的兩個端點。
我們可以先找出從右到左第乙個正數作為尋找i的起點(如果乙個正數都找不到那顯然就是l=0,最大子段和=0),
然後按照上述原理不斷向左延拓i;找j也是同理:先找從左到右第乙個正數然後向右擴充套件。把**貼上來
#include #define max 100//巨集定義要尋找的序列個數最大值
int fineleft(int d,int n);//尋找最大子段的左下標
int fineright(int d,int n);//尋找最大子段的右下標
int main();
int n;
int i;
int left,right;
scanf("%d",&n);
for (i=0;iright)
n=0;//這是我寫**節省空間的一種方式,n下面將儲存最大子段和
for(i=left;i<=reft;i++)
n+=d[i];
printf("%d/nbegin=%d,end=%d/n",n,left+1,right+1);
return 0;
}int findright(int d,int n)
} }return right;
}int fineleft(int d,int n)
} }return left;
}
//這裡的擴充套件思想可以幫助理解分治策略的第三種情況(從中間往兩邊擴充套件)。
本方法至多只需三次遍歷,一次往左,一次往右,一次最大子段求和。時間複雜度為o(n)。不過寫法應該還可以再改進,找左右端點的函式應該可以抽象出乙個模型。
最大子段和的兩種解法
1.貪心和動態規劃 實質一樣。前面和sum i 1 0sum 0 sumi 1 0就丟掉,然後加上a ia i ai 就是以a ia i ai 結尾的最大子段和。2.分治法。遞迴到l r l rl r時,顯然最大子段和是a ia i ai 對於區間 l,r l,r l,r 我們需要維護4個變數。1....
最大子段和詳解
問題的提出 給定n個整數 可能為負數 組成的序列a 1 a 2 a 3 a n 求該序列如a i a i 1 a j 的子段和的最大值。當所給的整均為負數時定義子段和為0,依此定義,所求的最優值為 max,1 i j n 例如,當 a1,a2,a3,a4,a4,a6 2,11,4,13,5,2 時,...
最大子段和詳解
最大子段和問題 maximum interval sum 有時也稱lis 經典的動態規劃問題,幾乎所有的演算法教材都會提到.本文將分析最大子段和問題的幾種不同效率的解法,以及最大子段和問題的擴充套件和運用.一.問題描述 給定長度為n的整數序列,a 1 n 求 1,n 某個子區間 i j 使得a i ...