问题描述:给定一个包含N个元素的数据A(A[0], A[1], A[2]...A[N-1]),这个数组自然有很多子数组,那么,这些子数组之和的最大值是什么?
Example: 数组[1, -2, 3, 5, -3, 2],最大的子数据是[3, 5],和为8。
解法一:穷举法
最直接,也是最简单的方法,穷举子数组A[i:j]的和,找出一个最大的,算法的时间复杂度O(N2),算法的代码如下:
#include <iostream> #include <climits> using namespace std; int max_sub_sum(const int* A, int n) { int max_sum = INT_MIN; for (int i = 0; i < n; i++) { int sub_sum = 0; for (int j = i; j < n; j++) { sub_sum += A[j]; //sum(i,j) if ( max_sum < sub_sum) { max_sum = sub_sum; } } } return max_sum; } int main() { int A[] = {1, -2, 3, 5, -3, 2}; int max_sum = max_sub_sum(A, sizeof(A) / sizeof(int)); cout<<"max sub sum:"<<max_sum<<endl; system("pause"); return 0; }
解法二:分治法
将数组分成两个子数组:A[0:n/2]和A[n/2 + 1, n]那么最大的组数据之和,必然出现在以下三种情况;
1)最大子段与A[0:n/2]重合;
2)最大子段与A[n/2 + 1, n]重合
3)最大子段夸过A[0:n/2]和A[n/2 + 1, n-1]两段。
对于1)、2)两种情况可以递归解决,而对于第三种情况,我们只要找到A[0:n/2]以n/2为终点最大和,A[n/2 + 1, n-1]以n/2 + 1为起点的最大和,两种相加即可。
算法的时间复杂度为O(NlogN),代码如下:
#include <iostream> #include <climits> using namespace std; int max_sub_sum(int* A, int i, int j) { if (i == j) { return A[i]; } int mid = (i + j) / 2; int max_sum = max(max_sub_sum(A, i, mid), max_sub_sum(A, mid+1, j)); int left_mid_max_sum = INT_MIN; int mid_sum = 0; for (int k =mid; k >= i; k--) { mid_sum += A[k]; if (left_mid_max_sum < mid_sum) { left_mid_max_sum = mid_sum; } } int right_mid_max_sum = INT_MIN; mid_sum = 0; for (int k = mid+1; k <= j; k++) { mid_sum += A[k]; if (right_mid_max_sum < mid_sum) { right_mid_max_sum = mid_sum; } } max_sum = max(max_sum, left_mid_max_sum + right_mid_max_sum); return max_sum; } int main() { int A[] = {1, -2, 3, 5, -3, 2}; int max_sum = max_sub_sum(A, 0, (sizeof(A) / sizeof(int)) -1); cout<<"max sub sum:"<<max_sum<<endl; system("pause"); return 0; }
解法三:动态规划
动态规划问题依赖于两个基本因素:1)最优子结构;2)重叠结构。首先定义两个量All(i)和Start(i),All(i)表示从A[i:n-1]数组中最大的子段和,Start(i)则表示了以下表i作为起始位置的最大子段和,那么该问题的最优的递归表达式:
All(i) = max(All(i+1), start(i))
这个公式表示,All(i)取:从i下标开始的字段数组start(i),从i+1开始的最大字段和All(i+1)之间的最大值, start(i)的定义如下:
start(i) = max(A[i], A[i] + start(i+1))
该解法的代码如下:
#include <iostream> #include <climits> using namespace std; int max_sub_sum(int* A, int n) { int all = A[n-1]; int start = A[n -1]; for (int i = n -2; i >= 0; i--) { start = max(A[i], A[i] + start); //start(i) = max(A[i], start(i+1)) all = max(all, start); } return all; } int main() { int A[] = {1, -2, 3, 5, -3, 2}; int max_sum = max_sub_sum(A, sizeof(A) / sizeof(int)); cout<<"max sub sum:"<<max_sum<<endl; system("pause"); return 0; }
最后一种方法是淳朴的迭代算法。
迭代算法看起来简单,但是这个问题但有着极其惊人的疗效:1)用变量per_sum保存当前以i-1作为结束下标的字段数组和,如果per_sum小于0(前面的部分起到了“负面”作用,需要舍弃),从下标i开始从新字段数组,于此同时,不断比较每次字段数组的最大值,当迭代结束是,便会得到字段数组的组大和。
#include <iostream> #include <climits> using namespace std; int max_sub_sum(int* A, int n) { int max_sum = INT_MIN; int sum = 0; for (int i = 0; i < n; i++) { if (sum < 0) //前面的元素提到了负面作用,需要舍弃 { sum = A[i]; } else { sum += A[i]; //前面的元素提到了正面作用,需要保留 } if (sum > max_sum) { max_sum = sum; } } return max_sum; } int main() { int A[] = {1, -2, 3, 5, -3, 2}; int max_sum = max_sub_sum(A, sizeof(A) / sizeof(int)); cout<<"max sub sum:"<<max_sum<<endl; system("pause"); return 0; }