最大子序列和

【题目】

给定(可能有负数)整数a(1)、a(2)、……a(n),求 a(1)+a(2)+……+a(j)的最大值。
为方便起见,若所有的整数为负数,则最大子序列和为0.

也就是:在一系列整数中,找出连续的若干个整数,这若干个整数之和 最大。

【代码实现】


  • 方法一:穷举法

  • 穷举所有可能,由于嵌套三层 for 循环,运行时间 O(N^3)
  • 算法思想:算出每个子序列的和,即算出序列中第 i 个到第 j 个数的和 (j >= i) ,并进行比较

  • C 语言版:函数原型为 int maxSubSum(int a[]);

#include <stdio.h>

int maxSubSum(int a[]){
int maxSum = 0;
int sum, i, j, k;
int len = sizeof(a) / sizeof(int);
for(i = 0; i < len; i++){
for(j = i; j < len; j++){
sum = 0;
for(k = i; k <= j; k++){
sum += a[k];//计算 a[i] 到 a[j] 的和
}
if(sum > maxSum){
maxSum = sum;
}
}
}
return maxSum;
}

int main(){
int a[] = {-2, 11, -4, 13, -5, -2};
int max = maxSubSum(a);
printf("%d\n",max);
return 0;
}
  • C 语言版:函数原型为 int maxSubSum(int a[], int n);
#include <stdio.h>

int maxSubSum(int a[], int n){
int maxSum = 0;
int sum, i, j, k;
for(i = 0; i < n; i++){
for(j = i; j < n; j++){
sum = 0;
for(k = i; k <= j; k++){
sum += a[k];//计算 a[i] 到 a[j] 的和
}
if(sum > maxSum){
maxSum = sum;
}
}
}
return maxSum;
}

int main(){
//int a[] = {-2, 11, -4, 13, -5, -2};
int a[] = { -2, 4, -3, 5, 7, -1, 8, 1 };
int len = sizeof(a) / sizeof(int);//有负数所以 strlen(a) 不能用
int max = maxSubSum(a, len);
printf("%d\n",max);
return 0;
}

  • 方法二:

  • 在第一种的基础上简化,撤除一层 for 循环,运行时间 O(N^2)
  • 算法思想:第一个算法的第三个 for 循环中有大量不必要的重复计算,如:计算 i 到 j 的和,然而 i 到 j-1 的和在前一次的循环中已经计算过,无需重复计算,故该 for 循环可以去掉

  • C 语言版

#include <stdio.h>

int maxSubSum(int a[], int n){
int maxSum = 0;
int sum, i, j;
for(i = 0; i < n; i++){
sum = 0;
for(j = i; j < n; j++){
sum += a[j];
if(sum > maxSum){
maxSum = sum;
}
}
}
return maxSum;
}

int main(){
//int a[] = {-2, 11, -4, 13, -5, -2};
int a[] = { -2, 4, -3, 5, 7, -1, 8, 1 };
int len = sizeof(a) / sizeof(int);//有负数所以 strlen(a) 不能用
int max = maxSubSum(a, len);
printf("%d\n",max);
return 0;
}

  • 方法三:分而治之

  • 算法思想:把问题分成两个大致相等的子问题,然后递归地对它们求解,这是“分”的部分。“治”阶段将两个子问题的解修补到一起并可能再做些少量的附加工作,最后得到整个问题的解。
  • 在该问题中,如果把序列从中间分为两部分,那么最大子序列和可能在三处出现,要么整个出现在输入数据的左半部,要么整个出现在右半部,要么跨越分界线。前两种情况可以递归求解,第三种情况的最大和可以通过求出前半部分(包括前半部分的最后一个元素)的最大和以及后半部分(包含后半部分的第一个元素)的最大和而得到,此时将两个和相加。
  • 运行时间 O( N log N )

  • C 语言版

#include <stdio.h>

int maxSubSum(int a[], int left, int right){
// 判断是否只有一个元素
if (left == right) {
if (a[left] > 0) {
return a[left];
} else {
return 0;
}
}
int center = (left + right) / 2;
int maxLeftSum = maxSubSum(a, left, center);
int maxRightSum = maxSubSum(a, center + 1, right);
// 左端处理
int maxLeftBorderSum = 0;
int leftBorderSum = 0;
int i;
for (i = center; i >= left; i--) {
leftBorderSum += a[i];
if (leftBorderSum > maxLeftBorderSum) {
maxLeftBorderSum = leftBorderSum;
}
}

// 右端处理
int maxRightBorderSum = 0;
int rightBorderSum = 0;
for (i = center + 1; i <= right; i++) {
rightBorderSum += a[i];
if (rightBorderSum > maxRightBorderSum) {
maxRightBorderSum = rightBorderSum;
}
}
// 返回最大值
int maxBorderSum = maxLeftBorderSum + maxRightBorderSum;
return maxBorderSum > maxLeftSum ? maxBorderSum > maxRightSum ? maxBorderSum : maxRightSum
: maxLeftSum > maxRightSum ? maxLeftSum : maxRightSum;
}

int main(){
//int a[] = {-2, 11, -4, 13, -5, -2};
int a[] = { -2, 4, -3, 5, 7, -1, 8, 1 };
int max = maxSubSum(a, 0, sizeof(a) / sizeof(int) - 1);
printf("%d\n",max);
return 0;
}

  • 方法四:最优起点,扫描法

  • 算法思想:设 a[i] 为和最大序列的起点,则如果 a[i] 是负的,那么它不可能代表最优序列的起点,因为任何包含 a[i] 作为起点的子序列都可以通过 a[i+1] 作起点而得到改进。
  • 类似的,任何负的子序列也不可能是最优子序列的前缀。
  • 运行时间:O(N)

  • C 语言版

#include <stdio.h>

int maxSubSum(int a[], int n){
int maxSum = 0;
int sum = a[0], i;
/*考虑如果全是负数,那么返回最大的负数,
如果最后的和为正,那么就使用扫描法*/
for(i = 1; i < n; i++){
if(sum < 0){//当前数小于0,换为下一个数
sum = a[i];
}else{
sum += a[i];
}
if(sum > maxSum){
maxSum = sum;
}
}
return maxSum;
}

int main(){
//int a[] = {-2, 11, -4, 13, -5, -2};
int a[] = { -2, 4, -3, 5, 7, -1, 8, 1 };
int len = sizeof(a) / sizeof(int);//有负数所以 strlen(a) 不能用
int max = maxSubSum(a, len);
printf("%d\n",max);
return 0;
}