问题描述:
输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,
因此输出为该子数组的和18。
思路分析:
由于时间复杂度要求为O(n),所以程序只能有一重for循环。
作如下分析:
如数组:1, -2, 3, 10, -4, 7, 2, -5
我们从左向右遍历:
我们让sum表示当前子数组的和,sub来表示最大子数组,max表示最大子数组的和。 start_index, end_index分别表示最大子数组在数组中的下标。
第一轮:sum = 1, sub = {1}, max = 1,start_index = 0, end_index = 0
第二轮:sum = -1, sub = {1}, max = 1, start_index = 0, end_index = 0
注意,此时我们看到,由于array[1]=-2, 这个元素的引入导致了sum变为负数,也就是说这个位置是一个潜在的分界点。
最大的子数组可能是-2之前出现的,也可能是-2之后出现的,但绝对不可能跨过-2.
此时,我们将sum清零。
第三轮:sum = 3, sub = {3}, max = 3, start_index = 2, end_index = 2
第四轮:sum = 13, sub = {3,10}, max = 13, start_index = 2, end_index = 3
第五轮:sum = 9, sub = {3, 10}, max = 13, start_index = 2, end_index = 3
注意,此时出现的-4和之前出现的-2有不同之处,-2的出现导致子数组的和为负,也就是说它的出现抵消掉出前面所有的贡献。
但-4的出现并没有让sum为负,所以后面的数有可能会补偿回来,使得sum继续增大。
。。。。。。。
通过上面的分析可以发现:
当sum为负时是一个转折点,此时最大子数组有可能就是前面得出和最大子数组,也有可能出现在后面还没有遍历到的数组之中,但绝对不会横跨当前使sum变为负的这个数。
故我们的策略就很简单了:
1. 遍历并累加和, 得到当前子数组之和sum
2. 如果sum < 0 且当前元素不是数组的最后一个,记录下这个位置index,它有可能是最大子数组的起始点, sum值清零。
3. 如果sum > max, max = sum, start_index = index, end_index = i
4. 重复1
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int get_max_sub(int *arr, int len)
{
int i;
int start_index, end_index;
int sum, max_sum;
int index;
start_index = 0;
end_index = 0;
sum = 0;
max_sum = 0;
index = 0;
for (i = 0; i < len; i++) {
sum += arr[i];
if (sum < 0){
sum = 0;
if (i + 1 < len){
index = i+1;
}
}
if (sum > max_sum){
max_sum = sum;
start_index = index;
end_index = i;
}
}
printf("The max sub array is: ");
for (i = start_index; i <= end_index; i++) {
printf("%d,", arr[i]);
}
printf("\nThe max sum is: %d.\n", max_sum);
return max_sum;
}
int main()
{
int data_array[] = {1, -2, 3, 10, -4, 7, 2, -5};
int len = sizeof(data_array)/sizeof(int);
get_max_sub(data_array, len);
return 0;
}