引言:
最大连续子序列问题可以很好的让我们了解算法对于程序设计的重要性,因此会在诸多面试笔试题目中考到,现在对它进行一个总结。
问题描述:
求数组中最大连续子序列和。例如给定数组A={4,-3, 5,-2,-1, 2, 6,-2},则最大子序列和为11,即11=4+(-3)+5+(-2)+(-1)+2+6。
解法1——时间复杂度O(N^3)
首先能想到的算法就是将所有的连续子序列和求出来,然后比较它们中最大的值将其返回。求所有子序列我们可以以每个子序列中第一个元素为循环变量。
int MaxSubsequenceSum(const int A[], int N) { int ThisSum, MaxSum, i, j, k; MaxSum = 0; for(i = 0; i < N; i++) for(j = i; j < N; j++) { ThisSum = 0; for(k = i; k <= j; k++) ThisSum += A[k]; if(ThisSum > MaxSum) MaxSum = ThisSum; } return MaxSum; }
解法思路跟第一种相同,都是求出所有的子序列,对其逐个比较并求出最大者。只是修改了部分代码。
int MaxSubsequenceSum(const int A[], int N) { int ThisSum, MaxSum, i, j; MaxSum = 0; for(i = 0; i < N; i++){ ThisSum = 0; for(j = i; j < N; j++) { ThisSum += A[j]; if(ThisSum > MaxSum) MaxSum = ThisSum; } } return MaxSum; }
采用“分治算法”求解该问题。其想法是把问题分成两个大致相等的子问题,然后递归地对它们求解,这是"分"部分。"治"阶段将两个子问题的解合并到一起请可能再做些少量的附加工作,最后得到整个问题的解。此例中,最大子序列和可能在三处出现。或者整个出现的输入数据的左半部,或者整个出现的右半部,或者跨越输入数据的中部从而占据左右两半部分。前两种情况可以递归求解。第三种情况的最大和可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而得到。然后将这两个和加在一起。
int Max3(int max1, int max2, int max3) { int max = max1; if(max < max2) max = max2; if(max < max3) max = max3; return max; } //分治算法的实现 int MaxSubSum(const int A[], int Left, int Right) { int MaxLeftSum, MaxRightSum; int MaxLeftBorderSum, MaxRightBorderSum; int LeftBorderSum, RightBorderSum; int Center, i; if(Left == Right) if(A[Left] > 0) return A[Left]; else return 0; Center = (Left + Right) / 2; MaxLeftSum = MaxSubSum(A, Left, Center); MaxRightSum = MaxSubSum(A, Center + 1, Right); MaxLeftBorderSum = 0; LeftBorderSum = 0; for(i = Center; i >= Left; i--){ LeftBorderSum += A[i]; if(LeftBorderSum > MaxLeftBorderSum) MaxLeftBorderSum = LeftBorderSum; } MaxRightBorderSum = 0; RightBorderSum = 0; for(i = Center + 1; i <= Right; i++){ RightBorderSum += A[i]; if(RightBorderSum > MaxRightBorderSum) MaxRightBorderSum = RightBorderSum; } return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum); } int MaxSubsequenceSum(const int A[], int N) { return MaxSubSum(A, 0, N-1); }
采用动态规划算法。代码如下:在这一遍扫描数组当中,从左到右记录当前子序列的和ThisSum,若这个和不断增加,那么最大子序列的和MaxSum也不断增加(不断更新MaxSum)。如果往前扫描中遇到负数,那么当前子序列的和将会减小。此时ThisSum将会小于MaxSum,当然MaxSum也就不更新。如果ThisSum降到0时,说明前面已经扫描的那一段就可以抛弃了,这时将ThisSum置为0。然后,ThisSum将从后面开始将这个子段进行分析,若有比当前MaxSum大的子段,继续更新MaxSum。这样一趟扫描结果也就出来了。
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; else if(ThisSum < 0) ThisSum = 0; } return MaxSum; }
注意:
对于所有的元素都是负数的序列,应该考虑是不是将最大子序列设为0的问题。上面的算法中大多是这么假设的。