題目一:
給定一個整型數組,數組中有正有負,求最大連續子序列的和。
解法:
利用動態規划的思想。
設f(n)表示以a[n]為子序列最后一個元素的最大和,則可以有下面的規則:
(1)當f(n-1)<0時,f(n)=a[n];
(2)當n!=0且f(n-1)>0時,f(n)=f(n-1)+a[n]。
用一個nGreatestNum來記錄最大值,每次與f(n)進行比較,不斷更新即可。
題目二:
給定一個二維數組,數組中有正有負,求最大子矩陣的和。
解法:
仍然用動態規划的思想。
首先,將二維問題降維處理:
例如,用2 維數組a[1 : m][1 : n]表示給定的m行n列的整數矩陣。子數組a[i1 : i2][j1 : j2]表示左上角和右下角行列坐標分別為(i1, j1)和(i2, j2)的子矩陣。
先按照行排列出所有可能區間,然后,再去求列的范圍。
更詳細的,當行區間確定之后,剩下就是確定列區間了,一旦確定列區間,最大子矩陣就確定了。
當行區間確定之后,求列區間的方法,可以轉化成一維數組的最大連續子序列的問題:對行區間[i1, j1],依次對列進行求和,就得到以n個列數據和的數組,根據最大連續子序列的和的求法,就可以獲得連續子序列最大和。
比如如下數組,當確定第1行到第3行時,然后將每一列相加得到新的一維數組,按照題目一的方法可以求出第1行和第3行之間的最大子矩陣:
-2 | 3 | -4 | 7 | -2 |
-3 | 9 | 9 | 3 | 1 |
-4 | -1 | 0 | 2 | 4 |
9 | 8 | -7 | 2 | 5 |
新的一維數組是:
-9 | 11 | 5 | 12 | 3 |
仍然用nGreatestNum來記錄最大值,算出一個子矩陣的和,就進行比較即可。
/* 題目描述:求一個n*n二維矩陣的最大子矩陣,maxSum。 */ #include<iostream> #include<vector> #include<cstdio> #include<cstring> #include<assert.h> using namespace std; //Problem C //2012-7-10 //by frank const int N = 103; const int INF = -9999; /* 算法思想:對於一維的數組,我們可以很容易用動態規划的方法求得最大子數組; 所以我們將i=[0...n], j[i..n]枚舉所有行的可能,然后再對每一種可能(此時可以 將它看做是一維數組的情況),用DP求得其最大子數組。 算法時間復雜度o(n^3)。 */ int maxSubArray(int a[], int n) { assert(a!=NULL && n>0); int cur = 0; int max = INF; for(int i=0; i<n; i++) { cur += a[i]; //當cur小於0時,應該重新開始計數. if(cur < 0) cur = 0; if(cur > max) max = cur; } return max; } int findMaxSubMatrix(int a[][N], int n) { int tmpSum[N]; int max = INF; //枚舉所有行的可能組合。 //遍歷所有可能存在的兩行,當確定兩行之后,如r1,r2這兩行, //然后將處於這兩行之間的每一列col[r1][i]~col[r2][i]相加存放在tmpsum中 for(int i=0; i<n; i++) { //將tmpSum清零。 memset(tmpSum,0,sizeof(tmpSum)); for(int j=i; j<n; j++) { //加上當前行的元素。 for(int k=0; k<n; k++) tmpSum[k] += a[j][k]; int tmpMax = maxSubArray(tmpSum, n); if(tmpMax > max) max = tmpMax; } } return max; } int main() { int a[N][N]; int n = 0; while(cin>>n && n) { for(int i=0; i<n; i++) for(int j=0; j<n; j++) cin>>a[i][j]; cout<<findMaxSubMatrix(a, n)<<endl; } return 0; }
復雜度分析:
(1)排列出行區間,復雜度為O(M*M);
(2)而求得最大子序列的和復雜度為O(N);
(3)對於行區間確定之后對列求和的復雜度呢?
這里采用“部分和”的做法。
用BC[i][j]表示0到i行、0到j列的總和。
那么對於行區間r->l,求第i列的和:BC[l][i] - B[r-1][i] - B[l][i-1] + B[r-1][i-1]。
而求“部分和”僅需要O(N*M)。可以預先計算好。
因此,算法復雜度為O(N*M*M)。
該方法的應用舉例:POJ 2479
題意解釋:
給定n個數,求兩段連續不重疊子段的最大和。比如1 -1 2 2 3 -3 4 -4 5 -5結果就是 {2,2,3,-3,4} 和 {5},也就是兩者的和13。
選題原因:
此題是對動態規划中的一個基礎知識點求最大字段和的一個簡單應用,難度不太大,比較具有代表性,是基礎題型。求最大字段和的方法有很多,但DP是最高效的,時間效率為O(n)。有關求最大字段和的詳細介紹參見王曉東《算法設計與分析》第三版59頁相關部分。這里僅附上求最大字段和的狀態轉移方程:b[j] = max {b[j-1] + a[j], a[j]}, 1 <= j <= n。
解題思路:
先對數字串從左向右依次求出每段的連續子序列的最大字段和,並將其存入數組array[i]中(i為對應位置),再從右向左用同樣的方法求一次最大字段和,並將每個子段i~n的和與對應的另一半1~i-1相加,求出最大值。也就是對每個位置i來說,求出[1~i-1]的最大子段和以及[i~n]的最大子段和,再相加起來,求最大的一個就行了。
與基礎的求最大字段和不同的是,該題需要對每個子段記錄其最大和,即存入數組array[i]中。
1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 int data[50100]; 7 int leftMax[50100];//從左往右計算最大的子串; 8 //int rightMax[50100];//從右向左計算最大的子串; 9 10 int T,n,i; 11 const int MIN = -999999999; 12 int sum,temp,ans; 13 cin >>T; 14 while (T--) 15 { 16 cin>>n; 17 //memset(leftMax,0,sizeof(leftMax)); 18 //memset(rightMax,0,sizeof(rightMax)); 19 sum = 0; 20 temp = MIN; 21 for (i=0;i<n;i++) 22 { 23 //cin >> data[i]; 24 scanf("%d", &data[i]);//scanf在需要輸入大量數據時所需時間更少。 25 26 sum+= data[i]; 27 if (sum>temp) 28 { 29 temp=sum; 30 } 31 leftMax[i]=temp; 32 if(sum<0)sum=0; 33 34 } 35 sum = 0; 36 temp = ans = MIN; 37 38 for (i=n-1;i>0;i--) 39 { 40 41 sum += data[i]; 42 if (sum>temp) 43 { 44 temp=sum; 45 } 46 if(ans<(leftMax[i-1]+temp)) 47 ans=leftMax[i-1]+temp;//判斷左右兩個子數組最大子串和相加是否增大 48 if(sum<0)sum=0; 49 50 } 51 52 //cout << ans <<endl; 53 printf("%d\n", ans); 54 55 56 } 57 return 0; 58 }