目录
题目描述
题目分析
图一乐之超级遍历法
聪明了一些之还是遍历法
递归分治法
动态规划解法
联机算法
写在最后
题目描述
题目分析给定一个数字序列,求i, j (1 ≤ i ≤ j ≤ n),使得最大,输出这个最大和。
虽然这篇博客旨在讲解动态规划解法,但我们还是先来看看各种解法都有哪些:
图一乐之超级遍历法#includeusing namespace std; const int N = 1010; int num[N]; int MaxSubSequenceSum(const int A[], int N){ int ThisSum, MaxSum = 0; for(int i = 0;i < N; i ++){ for(int j = i;j < N;j ++){ ThisSum = 0; for(int k = i;k <= j;k ++){ ThisSum += A[k]; if(ThisSum > MaxSum){ MaxSum = ThisSum; } } } } return MaxSum; } int main(){ int n; cin >> n; for(int i = 0; i < n; i ++) cin >> num[i]; cout << MaxSubSequenceSum(num, n) << endl; return 0; }
最基础的想法,就是穷举所有可能,时间复杂度为
聪明了一些之还是遍历法第一种方法涉及了太多的重复运算,笨到令人发指的程度。因此我们可以考虑撤掉一个for循环进行优化。
#includeusing namespace std; const int N = 1010; int num[N]; int MaxSubSequenceSum(const int A[], int N){ int ThisSum, MaxSum = 0; for(int i = 0;i < N; i ++){ ThisSum = 0; for(int j = i; j MaxSum) MaxSum = ThisSum; } } return MaxSum; } int main(){ int n; cin >> n; for(int i = 0; i < n; i ++) cin >> num[i]; cout << MaxSubSequenceSum(num, n) << endl; return 0; }
现在,时间复杂度变成了。
递归分治法在问题中,最大连续子序列无非出现在三个地方:
- 全部出现在序列的左半边;全部出现在序列的右半边;出现在中部,跨越左右半边
对于前两种情况,我们可以采用递归求解的方式,而第三种情况的最大和可以通过求出左半边的最大和(包含左半边的最后一个元素)加上右半边的最大和(包含右半边的第一个元素)而得到。易知分治法的复杂度为,又因为for循环最多只有一层,所以该算法的复杂度为。
#include动态规划解法using namespace std; const int N = 1010; int num[N]; int MaxSubSequenceSum(const int A[], int Left, int Right){ int MaxLeftSum = 0, MaxRightSum = 0; int MaxLeftBorderSum = 0, MaxRightBorderSum = 0; int LeftBorderSum = 0, RightBorderSum = 0; int Center; if(Left == Right){ if(A[Left] > 0) return A[Left]; else return 0; } Center = (Right + Left) / 2; MaxLeftSum = MaxSubSequenceSum(A, Left, Center); MaxRightSum = MaxSubSequenceSum(A, Center + 1, Right); for(int i = Center; i >= Left; i --){ LeftBorderSum += A[i]; if(LeftBorderSum > MaxLeftBorderSum) MaxLeftBorderSum = LeftBorderSum; } for(int i = Center + 1; i <= Right; i ++){ RightBorderSum += A[i]; if(RightBorderSum > MaxRightBorderSum) MaxRightBorderSum = RightBorderSum; } return max(max(MaxLeftSum, MaxRightSum), MaxRightBorderSum + MaxLeftBorderSum); } int main(){ int n; cin >> n; for(int i = 0; i < n; i ++) cin >> num[i]; cout << MaxSubSequenceSum(num, 0, n - 1) << endl; return 0; } //go to hell
上面的分治递归求解是不是感觉很高大上,把C++写出了Java的感觉?
事实上费力不讨好,还不是被动态规划一棒子撂倒。
下面,我们考虑用标准的动态规划来求解。
现进行如下思考:
1.令状态dp[i]表示以A[i]作为末尾的连续序列的最大和,所求的东西就是dp数组中的最大值。
2.做如下考虑:因为dp[i]要求是必须以A[i]结尾的连续序列,只有两种情况:
这个最大和的连续序列只有一个元素,即以A[i]开始,以A[i]结尾;这个最大和的连续序列有多个元素
于是得到状态转移方程:
这个式子只和i与i之前的元素有关,边界条件是dp[1] = A[1],dp数组的最大值就是答案。
算法的时间复杂度为
代码如下:
#include联机算法#include using namespace std; const int N = 10010; int A[N], dp[N]; int main(){ ios::sync_with_stdio(false); int n; cin >> n; for(int i = 1; i <= n; i ++) { cin >> A[i]; } dp[1] = A[1]; for(int i = 2; i <= n; i ++) { dp[i] = max(A[i], dp[i - 1] + A[i]); } int ans = 0; for(int i = 1; i <= n; i ++) { ans = max(dp[i], ans); } cout << ans << 'n'; return 0; }
个人认为最简单也最好想,本质上是动态规划的思想。
该算法对所有数据遍历一遍,一旦A[ i ]被读入并被处理,它就不再需要被记忆。由于该问题不需要最大序列和的坐标输出,所以我们不需要记忆序列位置,仅仅记忆当前最大序列和的值即可。
算法的时间复杂度为
代码如下:
#include写在最后using namespace std; const int N = 1010; int num[N]; int MaxSubSequenceSum(const int A[ ], int N){ int ThisSum, MaxSum, j; ThisSum = MaxSum = 0; for (j= 0; j < N; j++) { ThisSum += A[j]; if (ThisSum > MaxSum) MaxSum = ThisSum; if(ThisSum < 0) ThisSum = 0; } return MaxSum; } int main(){ int n; cin >> n; for(int i = 0; i < n; i ++) cin >> num[i]; cout << MaxSubSequenceSum(num, n - 1) << endl; return 0; }
最后...如果你有兴趣的话,欢迎来挑战这个问题的加强版本!
动态规划之最大两段连续子序列和_AryCra_07的博客-CSDN博客
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)