轉自:http://blog.csdn.net/leojames007/article/details/6798123題目:輸入一個整形數組,數組裡有正數也有負數。數組中連續的一個或多個整數組成一個子數組,每個子數組都有一個和。求所有子數組的和的最大值。要求時間複雜度為O(n)。 例如輸入的數組為1, -2, 3, 10, -4, 7, 2, -5,和最大的子數組為3, 10, -4, 7, 2,因此輸出為該子數組的和18。 如果不考慮時間複雜度,我們可以枚舉出所有子數組並求出他們的和。不過非常遺憾的是,由於長度為n的數組有O(n2),具體是n*(n+1)/2個子數組;而且求一個長度為n的數組的和的時間複雜度為O(n)。因此這種思路的時間是O(n3)。 解題思路:很容易理解,當我們加上一個正數時,和會增加;當我們加上一個負數時,和會減少。如果當前得到的和是個負數,那麼這個和在接下來的累加中應該拋棄並重新清零,不然的話這個負數將會減少接下來的和。 代碼: ///////////////////////////////////////////Find the greatest sum of all sub-arrays
//Return value: if the input is valid, return true, otherwise return false
///////////////////////////////////////
bool FindGreatestSumOfSubArray
(
int *pData, // an array
unsigned int nLength, // the length of array
int &nGreatestSum // the greatest sum of all sub-arrays
)
{
// if the input is invalid, return false
if((pData == NULL) || (nLength == 0))
return false;
int nCurSum = nGreatestSum = 0;
for(unsigned int i = 0; i < nLength; ++i)
{
nCurSum += pData;
// if the current sum is negative, discard it
if(nCurSum < 0)
nCurSum = 0;
// if a greater sum is found, update the greatest sum
if(nCurSum > nGreatestSum)
nGreatestSum = nCurSum;
}
// if all data are negative, find the greatest element in the array
if(nGreatestSum == 0)
{
nGreatestSum = pData[0];
for(unsigned int i = 1; i < nLength; ++i)
{
if(pData[i] > nGreatestSum)
nGreatestSum = pData[i];
}
}
return true;
} 討論:· 函數的傳回值不是子數組和的最大值,而是一個判斷輸入是否有效標誌。如果函數傳回值的是子數組和的最大值,那麼當輸入一個null 指標是應該返回什麼呢?返回0?那這個函數的使用者怎麼區分輸入無效和子數組和的最大值剛好是0這兩中情況呢?基於這個考慮,本人認為把子數組和的最大值以引用的方式放到參數列表中,同時讓函數返回一個函數是否正常執行的標誌。
· 輸入有一類特殊情況需要特殊處理。當輸入數組中所有整數都是負數時,子數組和的最大值就是數組中的最大元素。 解法一:最直接的解法當然是窮舉遍曆了,把所有的子數組列出來,然後計算和。複雜度可以簡單的想出來:設定兩個變數i和j為子數組邊界,這兩個變數都要遍曆整個數組,然後還需要一個遊標k,來遍曆整個子數組以求和。所以總的複雜度是O(n^3)。代碼如下: 1 int MaxSubSum(int *A,int n)
2 {
3 int max = -INFINITE;
4 int sum = 0;
5 for (int i = 0 ; i < n ; i++)
6 {
7 for (int j = i ; j < n ; j++)
8 {
9 for (int k = i ; k <= j ; k++)
10 {
11 sum += A[k];
12 }
13 if (sum > max)
14 {
15 max = sum;
16 }
17 }
18 }
19 return max;
20 }
21
解法一改進版:仔細琢磨就會發現,其實不需要再使用k去遍曆子數組,因為每次j移動都會產生新的子數組,所以只要在每次j移動時進行一下比較,就不會把最大值漏掉。所以只有i和j移動,複雜度降低到O(n^2)。代碼如下: 1 int MaxSubSum(int *A,int n)
2 {
3 int max = -INFINITE;
4 int sum = 0;
5 for (int i = 0 ; i < n ; i++)
6 {
7 sum = 0;
8 for (int j = i ; j < n ; j++)
9 {
10 sum += A[j];
11 if (sum > max)
12 max = sum;
13 }
14 }
15 return max;
16 }
17
解法二:分治演算法跟二分尋找的思想相似,我們可以分情況討論這個問題是不是符合二分尋找的條件。情況1.這個滿足最大和的子數組全部在本數組的左半部或者右半部。例如:左半部A[i]……A[n/2-1]或者右半部A[n/2]……A[j]。這種情況下可以直接使用遞迴調用。情況2.滿足最大和的子數組跨過了本數組的中間點。例如:A[i]……A[n/2-1] A[n/2]……A[j]連續。則這種情況下只要在左半部尋找以A[n/2-1]結尾,在右半部尋找以A[n/2]開頭的兩個滿足最大和的連續數組,並求和即可。由於這個已知起點,只需要一個遊標即可,所以複雜度是2*O(n/2)=O(n)。綜合以上兩種情況,滿足分治演算法遞迴式:T(n)=2T(n/2)+O(n)=O(n*logn)。代碼如下:
1 int MaxSubSum(int *A,int Left,int Right)
2 {
3 //
4 int MaxLeftSum,MaxRightSum;
5 //左右子數組的和的最大值
6 int MaxLeftPartSum,MaxRightPartSum;
7 //臨時變數,用於儲存計算出來的和
8 int LeftPartSum,RightPartSum;
9 int Center,i;
10
11 //其中某一部分只有一個元素
12 if(Left == Right)
13 {
14 if(A[Left] > 0)
15 return A[Left];
16 else
17 return 0;
18 }
19
20 //遞迴調用。分別計算左右子數組的最大和子數組。
21 //即假設最大和子數組沒有被Center切割
22 Center = (Left+Right)/2;
23 MaxLeftSum = MaxSubSum(A,Left,Center);
24 MaxRightSum = MaxSubSum(A,Center+1,Right);
25
26 //假設最大和子數組被Center切開的情況
27 //那麼需要從Center開始向兩側計算
28 MaxLeftPartSum = 0;
29 LeftPartSum = 0;
30 for(i = Center ; i >= Left; --i )
31 {
32 LeftPartSum += A[i];
33 if(LeftPartSum > MaxLeftPartSum)
34 MaxLeftPartSum = LeftPartSum;
35 }
36 MaxRightPartSum = 0;
37 RightPartSum = 0;
38 for(i = Center+1 ; i <= Right ; ++i)
39 {
40 RightPartSum += A[i];
41 if(RightPartSum > MaxRightPartSum)
42 MaxRightPartSum = RightPartSum;
43 }
44 //返回三者中的最大值。
45 return max(max(MaxLeftSum,MaxRightSum),MaxLeftPartSum+MaxRightPartSum);
46 }
47
解法三:我們試著再觀察這個數組的特點,一個元素一個元素的看。根據A[0]是否在這個滿足最大和的子數組中,我們可以分為兩種情況。1. 在。那麼可以從A[0]開始求(比較容易)。2. 不在。那麼這種情況,又可以繼續分為兩種情況:A[1]在不在這個滿足最大和的子數組中。從這裡我們可以觀察出一種遞迴的特點,可以把一個規模為N的問題轉化為規模為N-1的問題。所以這個從A[0]到A[n-1]的最大和子數組問題分解成:1. 所求子數組中包含A[0]。如果不包含A[1],則A[0]自己滿足條件,此時Max(A[0]……A[n-1])=A[0]。如果包含A[1],則Max(A[0]……A[n-1])=A[0]+Max(A[1]……A[n-1])。2. 所求子數組中不包含A[0]。Max(A[0]……A[n-1])=Max(A[1]……A[n-1])。最終結果取以上三者的最大值即可,即Max(A[0]……A[n-1])=max( A[0], A[0]+Max(A[1]……A[n-1]), Max(A[1]……A[n-1]))。這個的複雜度為線性:因為只要把數組遍曆一遍即可。代碼如下: 1 int MaxSubSum(int *A,int n)
2 {
3 //假設滿足最大和的子數組就是從StartFrom[i]開始
4 int *StartFrom = new int[n];
5 memset(StartFrom,n,0);
6 StartFrom[n-1] = A[n-1];
7 //假設A[i]之後滿足最大和的子數組的和為Longest[i](不一定包括A[i])
8 int *Longest = new int[n];
9 memset(Longest,n,0);
10 Longest[n-1] = A[n-1];
11
12 for (int i = n-2 ; i >= 0 ; i--)
13 {
14 //如果從i開始,那麼要麼最大和只包括A[i]自己,要麼就是後面的那個序列連上A[i]
15 StartFrom[i] = max(A[i],A[i]+StartFrom[i+1]);
16 //最大和,要麼是從i開始的,要麼還是以前的
17 Longest[i] = max(StartFrom[i],Longest[i+1]);
18 }
19 //最後結果是在號元素中儲存
20 return Longest[0];
21 }
22
由於這種前後單元素的相關性,實際上不需要兩個數組來儲存這個資訊,只需要兩個變數即可,這樣可以減小程式的空間複雜度。代碼如下: 1 int MaxSubSum(int *A,int n)
2 {
3 //假設滿足最大和的子數組就是從StartFrom開始
4 int StartFrom = A[n-1];
5 //假設A[i]之後滿足最大和的子數組的和為Longest(不一定包括A[i])
6 int Longest = A[n-1];
7
8 for (int i = n-2 ; i >= 0 ; i--)
9 {
10 //如果從i開始,那麼要麼最大和只包括A[i]自己,要麼就是後面的那個序列連上A[i]
11 StartFrom = max(A[i],A[i]+StartFrom);
12 //最大和,要麼是從i開始的,要麼還是以前的
13 Longest = max(StartFrom,Longest);
14 }
15 //最後結果是在0號元素中儲存
16 return Longest;
17 }
輸出子序列的起點和終點,並輸出該子序列的和#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <malloc.h>
int main()
{
int *ip;
int j,length,max,sum;
int start1 = 0 ,start2 = 0;
printf("Please enter the array's length:");
scanf("%d",&length);
if((ip = (int*)malloc(length*sizeof(int)))==NULL)
{
fprintf(stderr,"Malloc memory failed !");
exit(1);
}
printf("Enter eath element:");
for(j = 0; j < length ; j ++)
scanf("%d",ip+j);
max = INT_MIN;
for(sum = j = 0; j < length; j ++)
{
sum += *(ip+j);
if(max < sum)
{
start1 = start2;
max = sum;
}
if(sum < 0){
start2 = j+1;
sum = 0;
}
}
for(j = start1,sum = 0; sum != max; j ++)
sum += *(ip+j);
printf("\nThe subsequence from %d to %d,max sum is %d\n",start1,j-1,max);
return 0;
}