1. 程式人生 > >程式設計師程式設計藝術-----第七章-----求連續子陣列的最大和

程式設計師程式設計藝術-----第七章-----求連續子陣列的最大和

 程式設計師程式設計藝術:第七章、求連續子陣列的最大和 


前奏

  • 希望更多的人能和我一樣,把本狂想曲系列中的任何一道面試題當做一道簡單的程式設計題或一個實質性的問題來看待,在閱讀本狂想曲系列的過程中,希望你能儘量暫時放下所有有關面試的一切包袱,潛心攻克每一道“程式設計題”,在解決程式設計題的過程中,好好享受程式設計帶來的無限樂趣,與思考帶來的無限激情。--By@July_____
  • 原狂想曲系列已更名為:程式設計師程式設計藝術系列。原狂想曲創作組更名為程式設計藝術室。程式設計藝術室致力於以下三點工作:1、針對一個問題,不斷尋找更高效的演算法,並予以程式設計實現。2、解決實際中會碰到的應用問題,如第十章、如何給10^7個數據量的磁碟檔案排序
    。3、經典演算法的研究與實現。總體突出一點:程式設計,如何高效的程式設計解決實際問題。歡迎有志者加入。


第一節、求子陣列的最大和
3.求子陣列的最大和
題目描述:
輸入一個整形陣列,數組裡有正數也有負數。
陣列中連續的一個或多個整陣列成一個子陣列,每個子陣列都有一個和。
求所有子陣列的和的最大值。要求時間複雜度為O(n)。

例如輸入的陣列為1, -2, 3, 10, -4, 7, 2, -5,和最大的子陣列為3, 10, -4, 7, 2,
因此輸出為該子陣列的和18。

分析:這個問題在各大公司面試中出現頻率之頻繁,被人引用次數之多,非一般面試題可與之匹敵。單憑這點,就沒有理由不入選狂想曲系列中了。此題曾作為本人之前整理的微軟100題中的第3題,至今反響也很大。ok,下面,咱們來一步一步分析這個題:
      1、

求一個數組的最大子陣列和,如此序列1, -2, 3, 10, -4, 7, 2, -5,我想最最直觀也是最野蠻的辦法便是,三個for迴圈三層遍歷,求出陣列中每一個子陣列的和,最終求出這些子陣列的最大的一個值。
記Sum[i, …, j]為陣列A中第i個元素到第j個元素的和(其中0 <= i <= j < n),遍歷所有可能的Sum[i, …, j],那麼時間複雜度為O(N^3):

//本段程式碼引自程式設計之美
int MaxSum(int* A, int n)
{
 int maximum = -INF; 
 int sum=0;   
 for(int i = 0; i < n; i++)
 {
  for(int j = i; j < n; j++)
  {
   for(int k = i; k <= j; k++)
   {
    sum += A[k];
   }
   if(sum > maximum)
    maximum = sum;

   sum=0;   //這裡要記得清零,否則的話sum最終存放的是所有子陣列的和。也就是程式設計之美上所說的bug。多謝蒼狼。
  }
 }
 return maximum;

      2、其實這個問題,在我之前上傳的微軟100題,答案V0.2版[第1-20題答案],便直接給出了以下O(N)的演算法:

  1. //[email protected] July 2010/10/18  
  2. //updated,2011.05.25.  
  3. #include <iostream.h>  
  4. int maxSum(int* a, int n)  
  5. {  
  6.     int sum=0;  
  7.     //其實要處理全是負數的情況,很簡單,如稍後下面第3點所見,直接把這句改成:"int sum=a[0]"即可  
  8.     //也可以不改,當全是負數的情況,直接返回0,也不見得不行。  
  9.     int b=0;  
  10.     for(int i=0; i<n; i++)  
  11.     {  
  12.         if(b<0)           //...  
  13.             b=a[i];  
  14.         else  
  15.             b+=a[i];  
  16.         if(sum<b)  
  17.             sum=b;  
  18.     }  
  19.     return sum;  
  20. }  
  21. int main()  
  22. {  
  23.     int a[10]={1, -2, 3, 10, -4, 7, 2, -5};  
  24.     //int a[]={-1,-2,-3,-4};  //測試全是負數的用例  
  25.     cout<<maxSum(a,8)<<endl;  
  26.     return 0;  
  27. }  
  28. /*------------------------------------- 
  29. 解釋下: 
  30. 例如輸入的陣列為1, -2, 3, 10, -4, 7, 2, -5, 
  31. 那麼最大的子陣列為3, 10, -4, 7, 2, 
  32. 因此輸出為該子陣列的和18。 
  33. 所有的東西都在以下倆行, 
  34. 即: 
  35. b  :  0  1  -1  3  13   9  16  18  13   
  36. sum:  0  1   1  3  13  13  16  18  18 
  37. 其實演算法很簡單,當前面的幾個數,加起來後,b<0後, 
  38. 把b重新賦值,置為下一個元素,b=a[i]。 
  39. 當b>sum,則更新sum=b; 
  40. 若b<sum,則sum保持原值,不更新。。July、10/31。 
  41. ----------------------------------*/  

      3、不少朋友看到上面的答案之後,認為上述思路2的程式碼,沒有處理全是負數的情況,當全是負數的情況時,我們可以讓程式返回0,也可以讓其返回最大的那個負數,下面便是前幾日重寫的,修改後的處理全是負數情況(返回最大的負數)的程式碼:

  1. //[email protected] July  
  2. //July、updated,2011.05.25。  
  3. #include <iostream.h>  
  4. #define n 4           //多定義了一個變數  
  5. int maxsum(int a[n])    
  6. //於此處,你能看到上述思路2程式碼(指標)的優勢  
  7. {  
  8.     int max=a[0];       //全負情況,返回最大數  
  9.     int sum=0;  
  10.     for(int j=0;j<n;j++)  
  11.     {  
  12.         if(sum>=0)     //如果加上某個元素,sum>=0的話,就加  
  13.             sum+=a[j];  
  14.         else     
  15.             sum=a[j];  //如果加上某個元素,sum<0了,就不加  
  16.         if(sum>max)  
  17.             max=sum;  
  18.     }  
  19.     return max;  
  20. }  
  21. int main()  
  22. {  
  23.     int a[]={-1,-2,-3,-4};  
  24.     cout<<maxsum(a)<<endl;  
  25.     return 0;  
  26. }  

      4、DP解法的具體方程:@flyinghearts:設sum[i] 為前i個元素中,包含第i個元素且和最大的連續子陣列,result 為已找到的子陣列中和最大的。對第i+1個元素有兩種選擇:做為新子陣列的第一個元素、放入前面找到的子陣列。
sum[i+1] = max(a[i+1], sum[i] + a[i+1])
result = max(result, sum[i])
 

擴充套件:
1、如果陣列是二維陣列,同樣要你求最大子陣列的和列?
2、如果是要你求子陣列的最大乘積列?
3、如果同時要求輸出子段的開始和結束列?

第二節、Data structures and Algorithm analysis in C

下面給出《Data structures and Algorithm analysis in C》中4種實現。

  1. //感謝網友firo  
  2. //July、2010.06.05。  
  3. //Algorithm 1:時間效率為O(n*n*n)  
  4. int MaxSubsequenceSum1(const int A[],int N)  
  5. {  
  6.     int ThisSum=0 ,MaxSum=0,i,j,k;  
  7.     for(i=0;i<N;i++)  
  8.         for(j=i;j<N;j++)  
  9.         {  
  10.             ThisSum=0;  
  11.             for(k=i;k<j;k++)  
  12.                 ThisSum+=A[k];  
  13.             if(ThisSum>MaxSum)  
  14.                 MaxSum=ThisSum;  
  15.         }  
  16.         return MaxSum;  
  17. }  
  18. //Algorithm 2:時間效率為O(n*n)  
  19. int MaxSubsequenceSum2(const int A[],int N)  
  20. {  
  21.     int ThisSum=0,MaxSum=0,i,j,k;  
  22.     for(i=0;i<N;i++)  
  23.     {  
  24.         ThisSum=0;  
  25.         for(j=i;j<N;j++)  
  26.         {  
  27.             ThisSum+=A[j];  
  28.             if(ThisSum>MaxSum)  
  29.                 MaxSum=ThisSum;  
  30.         }  
  31.     }  
  32.     return MaxSum;  
  33. }  
  34. //Algorithm 3:時間效率為O(n*log n)  
  35. //演算法3的主要思想:採用二分策略,將序列分成左右兩份。  
  36. //那麼最長子序列有三種可能出現的情況,即  
  37. //【1】只出現在左部分.  
  38. //【2】只出現在右部分。  
  39. //【3】出現在中間,同時涉及到左右兩部分。  
  40. //分情況討論之。  
  41. static int MaxSubSum(const int A[],int Left,int Right)  
  42. {  
  43.     int MaxLeftSum,MaxRightSum;              //左、右部分最大連續子序列值。對應情況【1】、【2】  
  44.     int MaxLeftBorderSum,MaxRightBorderSum;  //從中間分別到左右兩側的最大連續子序列值,對應case【3】。  
  45.     int LeftBorderSum,RightBorderSum;  
  46.     int Center,i;  
  47.     if(Left == Right)Base Case  
  48.         if(A[Left]>0)  
  49.             return A[Left];  
  50.         else  
  51.             return 0;  
  52.         Center=(Left+Right)/2;  
  53.         MaxLeftSum=MaxSubSum(A,Left,Center);  
  54.         MaxRightSum=MaxSubSum(A,Center+1,Right);  
  55.         MaxLeftBorderSum=0;  
  56.         LeftBorderSum=0;  
  57.         for(i=Center;i>=Left;i--)  
  58.         {  
  59.             LeftBorderSum+=A[i];  
  60.             if(LeftBorderSum>MaxLeftBorderSum)  
  61.                 MaxLeftBorderSum=LeftBorderSum;  
  62.         }  
  63.         MaxRightBorderSum=0;  
  64.         RightBorderSum=0;  
  65.         for(i=Center+1;i<=Right;i++)  
  66.         {  
  67.             RightBorderSum+=A[i];  
  68.             if(RightBorderSum>MaxRightBorderSum)  
  69.                 MaxRightBorderSum=RightBorderSum;  
  70.         }  
  71.         int max1=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;  
  72.         int max2=MaxLeftBorderSum+MaxRightBorderSum;  
  73.         return max1>max2?max1:max2;  
  74. }  
  75. //Algorithm 4:時間效率為O(n)  
  76. //同上述第一節中的思路3、和4。  
  77. int MaxSubsequenceSum(const int A[],int N)  
  78. {  
  79.     int ThisSum,MaxSum,j;  
  80.     ThisSum=MaxSum=0;  
  81.     for(j=0;j<N;j++)  
  82.     {  
  83.         ThisSum+=A[j];  
  84.         if(ThisSum>MaxSum)  
  85.             MaxSum=ThisSum;  
  86.         else if(ThisSum<0)  
  87.             ThisSum=0;  
  88.     }  
  89.     return MaxSum;  
  90. }   
  

本章完。