题目一:
给定一个整型数组,数组中有正有负,求最大连续子序列的和。
解法:
利用动态规划的思想。
设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 }