转自:http://blog.csdn.net/leojames007/article/details/6798123
题目:输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,因此输出为该子数组的和18。
如果不考虑时间复杂度,我们可以枚举出所有子数组并求出他们的和。不过非常遗憾的是,由于长度为n的数组有O(n
2),具体是n*(n+1)/2个子数组;而且求一个长度为n的数组的和的时间复杂度为O(n)。因此这种思路的时间是O(n
3)。
解题思路:很容易理解,当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。
代码:
/
//Find the greatest sum of all sub-arrays
//Return value: if the input is valid, return true, otherwise return false
///
bool FindGreatestSumOfSubArray
(
int *pData, // an array
unsigned int nLength, // the length of array
int &nGreatestSum // the greatest sum of all sub-arrays
)
{
// if the input is invalid, return false
if((pData == NULL) || (nLength == 0))
return false;
int nCurSum = nGreatestSum = 0;
for(unsigned int i = 0; i < nLength; ++i)
{
nCurSum += pData;
// if the current sum is negative, discard it
if(nCurSum < 0)
nCurSum = 0;
// if a greater sum is found, update the greatest sum
if(nCurSum > nGreatestSum)
nGreatestSum = nCurSum;
}
// if all data are negative, find the greatest element in the array
if(nGreatestSum == 0)
{
nGreatestSum = pData[0];
for(unsigned int i = 1; i < nLength; ++i)
{
if(pData[i] > nGreatestSum)
nGreatestSum = pData[i];
}
}
return true;
}
//Return value: if the input is valid, return true, otherwise return false
///
bool FindGreatestSumOfSubArray
(
int *pData, // an array
unsigned int nLength, // the length of array
int &nGreatestSum // the greatest sum of all sub-arrays
)
{
// if the input is invalid, return false
if((pData == NULL) || (nLength == 0))
return false;
int nCurSum = nGreatestSum = 0;
for(unsigned int i = 0; i < nLength; ++i)
{
nCurSum += pData;
// if the current sum is negative, discard it
if(nCurSum < 0)
nCurSum = 0;
// if a greater sum is found, update the greatest sum
if(nCurSum > nGreatestSum)
nGreatestSum = nCurSum;
}
// if all data are negative, find the greatest element in the array
if(nGreatestSum == 0)
{
nGreatestSum = pData[0];
for(unsigned int i = 1; i < nLength; ++i)
{
if(pData[i] > nGreatestSum)
nGreatestSum = pData[i];
}
}
return true;
}
讨论:
· 函数的返回值不是子数组和的最大值,而是一个判断输入是否有效的标志。如果函数返回值的是子数组和的最大值,那么当输入一个空指针是应该返回什么呢?返回0?那这个函数的用户怎么区分输入无效和子数组和的最大值刚好是0这两中情况呢?基于这个考虑,本人认为把子数组和的最大值以引用的方式放到参数列表中,同时让函数返回一个函数是否正常执行的标志。
· 输入有一类特殊情况需要特殊处理。当输入数组中所有整数都是负数时,子数组和的最大值就是数组中的最大元素。
· 输入有一类特殊情况需要特殊处理。当输入数组中所有整数都是负数时,子数组和的最大值就是数组中的最大元素。
解法一:
最直接的解法当然是穷举遍历了,把所有的子数组列出来,然后计算和。
复杂度可以简单的想出来:设置两个变量i和j为子数组边界,这两个变量都要遍历整个数组,然后还需要一个游标k,来遍历整个子数组以求和。所以总的复杂度是O(n^3)。
代码如下:
1 int MaxSubSum(int *A,int n)
2 {
3 int max = -INFINITE;
4 int sum = 0;
5 for (int i = 0 ; i < n ; i++)
6 {
7 for (int j = i ; j < n ; j++)
8 {
9 for (int k = i ; k <= j ; k++)
10 {
11 sum += A[k];
12 }
13 if (sum > max)
14 {
15 max = sum;
16 }
17 }
18 }
19 return max;
20 }
21
解法一改进版:
2 {
3 int max = -INFINITE;
4 int sum = 0;
5 for (int i = 0 ; i < n ; i++)
6 {
7 for (int j = i ; j < n ; j++)
8 {
9 for (int k = i ; k <= j ; k++)
10 {
11 sum += A[k];
12 }
13 if (sum > max)
14 {
15 max = sum;
16 }
17 }
18 }
19 return max;
20 }
21
解法一改进版:
仔细琢磨就会发现,其实不需要再使用k去遍历子数组,因为每次j移动都会产生新的子数组,所以只要在每次j移动时进行一下比较,就不会把最大值漏掉。所以只有i和j移动,复杂度降低到O(n^2)。
代码如下:
1 int MaxSubSum(int *A,int n)
2 {
3 int max = -INFINITE;
4 int sum = 0;
5 for (int i = 0 ; i < n ; i++)
6 {
7 sum = 0;
8 for (int j = i ; j < n ; j++)
9 {
10 sum += A[j];
11 if (sum > max)
12 max = sum;
13 }
14 }
15 return max;
16 }
17
解法二:分治算法
2 {
3 int max = -INFINITE;
4 int sum = 0;
5 for (int i = 0 ; i < n ; i++)
6 {
7 sum = 0;
8 for (int j = i ; j < n ; j++)
9 {
10 sum += A[j];
11 if (sum > max)
12 max = sum;
13 }
14 }
15 return max;
16 }
17
解法二:分治算法
跟二分查找的思想相似,我们可以分情况讨论这个问题是不是符合二分查找的条件。
情况1.这个满足最大和的子数组全部在本数组的左半部或者右半部。例如:左半部A[i]……A[n/2-1]或者右半部A[n/2]……A[j]。这种情况下可以直接使用递归调用。
情况2.满足最大和的子数组跨过了本数组的中间点。例如:A[i]……A[n/2-1] A[n/2]……A[j]连续。则这种情况下只要在左半部寻找以A[n/2-1]结尾,在右半部寻找以A[n/2]开头的两个满足最大和的连续数组,并求和即可。由于这个已知起点,只需要一个游标即可,所以复杂度是2*O(n/2)=O(n)。
综合以上两种情况,满足分治算法递归式:T(n)=2T(n/2)+O(n)=O(n*logn)。
代码如下:
1 int MaxSubSum(int *A,int Left,int Right)
2 {
3 //
4 int MaxLeftSum,MaxRightSum;
5 //左右子数组的和的最大值
6 int MaxLeftPartSum,MaxRightPartSum;
7 //临时变量,用于存储计算出来的和
8 int LeftPartSum,RightPartSum;
9 int Center,i;
10
11 //其中某一部分只有一个元素
12 if(Left == Right)
13 {
14 if(A[Left] > 0)
15 return A[Left];
16 else
17 return 0;
18 }
19
20 //递归调用。分别计算左右子数组的最大和子数组。
21 //即假设最大和子数组没有被Center切割
22 Center = (Left+Right)/2;
23 MaxLeftSum = MaxSubSum(A,Left,Center);
24 MaxRightSum = MaxSubSum(A,Center+1,Right);
25
26 //假设最大和子数组被Center切开的情况
27 //那么需要从Center开始向两侧计算
28 MaxLeftPartSum = 0;
29 LeftPartSum = 0;
30 for(i = Center ; i >= Left; --i )
31 {
32 LeftPartSum += A[i];
33 if(LeftPartSum > MaxLeftPartSum)
34 MaxLeftPartSum = LeftPartSum;
35 }
36 MaxRightPartSum = 0;
37 RightPartSum = 0;
38 for(i = Center+1 ; i <= Right ; ++i)
39 {
40 RightPartSum += A[i];
41 if(RightPartSum > MaxRightPartSum)
42 MaxRightPartSum = RightPartSum;
43 }
44 //返回三者中的最大值。
45 return max(max(MaxLeftSum,MaxRightSum),MaxLeftPartSum+MaxRightPartSum);
46 }
47
解法三:
1 int MaxSubSum(int *A,int Left,int Right)
2 {
3 //
4 int MaxLeftSum,MaxRightSum;
5 //左右子数组的和的最大值
6 int MaxLeftPartSum,MaxRightPartSum;
7 //临时变量,用于存储计算出来的和
8 int LeftPartSum,RightPartSum;
9 int Center,i;
10
11 //其中某一部分只有一个元素
12 if(Left == Right)
13 {
14 if(A[Left] > 0)
15 return A[Left];
16 else
17 return 0;
18 }
19
20 //递归调用。分别计算左右子数组的最大和子数组。
21 //即假设最大和子数组没有被Center切割
22 Center = (Left+Right)/2;
23 MaxLeftSum = MaxSubSum(A,Left,Center);
24 MaxRightSum = MaxSubSum(A,Center+1,Right);
25
26 //假设最大和子数组被Center切开的情况
27 //那么需要从Center开始向两侧计算
28 MaxLeftPartSum = 0;
29 LeftPartSum = 0;
30 for(i = Center ; i >= Left; --i )
31 {
32 LeftPartSum += A[i];
33 if(LeftPartSum > MaxLeftPartSum)
34 MaxLeftPartSum = LeftPartSum;
35 }
36 MaxRightPartSum = 0;
37 RightPartSum = 0;
38 for(i = Center+1 ; i <= Right ; ++i)
39 {
40 RightPartSum += A[i];
41 if(RightPartSum > MaxRightPartSum)
42 MaxRightPartSum = RightPartSum;
43 }
44 //返回三者中的最大值。
45 return max(max(MaxLeftSum,MaxRightSum),MaxLeftPartSum+MaxRightPartSum);
46 }
47
解法三:
我们试着再观察这个数组的特点,一个元素一个元素的看。
根据A[0]是否在这个满足最大和的子数组中,我们可以分为两种情况。
1. 在。那么可以从A[0]开始求(比较容易)。
2. 不在。那么这种情况,又可以继续分为两种情况:A[1]在不在这个满足最大和的子数组中。
从这里我们可以观察出一种递归的特点,可以把一个规模为N的问题转化为规模为N-1的问题。所以这个从A[0]到A[n-1]的最大和子数组问题分解成:
1. 所求子数组中包含A[0]。如果不包含A[1],则A[0]自己满足条件,此时Max(A[0]……A[n-1])=A[0]。如果包含A[1],则Max(A[0]……A[n-1])=A[0]+Max(A[1]……A[n-1])。
2. 所求子数组中不包含A[0]。Max(A[0]……A[n-1])=Max(A[1]……A[n-1])。
最终结果取以上三者的最大值即可,即Max(A[0]……A[n-1])=max( A[0], A[0]+Max(A[1]……A[n-1]), Max(A[1]……A[n-1]))。
这个的复杂度为线性:因为只要把数组遍历一遍即可。
代码如下:
1 int MaxSubSum(int *A,int n)
2 {
3 //假设满足最大和的子数组就是从StartFrom[i]开始
4 int *StartFrom = new int[n];
5 memset(StartFrom,n,0);
6 StartFrom[n-1] = A[n-1];
7 //假设A[i]之后满足最大和的子数组的和为Longest[i](不一定包括A[i])
8 int *Longest = new int[n];
9 memset(Longest,n,0);
10 Longest[n-1] = A[n-1];
11
12 for (int i = n-2 ; i >= 0 ; i--)
13 {
14 //如果从i开始,那么要么最大和只包括A[i]自己,要么就是后面的那个序列连上A[i]
15 StartFrom[i] = max(A[i],A[i]+StartFrom[i+1]);
16 //最大和,要么是从i开始的,要么还是以前的
17 Longest[i] = max(StartFrom[i],Longest[i+1]);
18 }
19 //最后结果是在号元素中保存
20 return Longest[0];
21 }
22
由于这种前后单元素的相关性,实际上不需要两个数组来储存这个信息,只需要两个变量即可,这样可以减小程序的空间复杂度。
代码如下: 1 int MaxSubSum(int *A,int n)
2 {
3 //假设满足最大和的子数组就是从StartFrom开始
4 int StartFrom = A[n-1];
5 //假设A[i]之后满足最大和的子数组的和为Longest(不一定包括A[i])
6 int Longest = A[n-1];
7
8 for (int i = n-2 ; i >= 0 ; i--)
9 {
10 //如果从i开始,那么要么最大和只包括A[i]自己,要么就是后面的那个序列连上A[i]
11 StartFrom = max(A[i],A[i]+StartFrom);
12 //最大和,要么是从i开始的,要么还是以前的
13 Longest = max(StartFrom,Longest);
14 }
15 //最后结果是在0号元素中保存
16 return Longest;
17 }
输出子序列的起点和终点,并输出该子序列的和
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <malloc.h>
int main()
#include <stdlib.h>
#include <limits.h>
#include <malloc.h>
int main()
{
int *ip;
int j,length,max,sum;
int start1 = 0 ,start2 = 0;
printf("Please enter the array's length:");
scanf("%d",&length);
if((ip = (int*)malloc(length*sizeof(int)))==NULL)
{
fprintf(stderr,"Malloc memory failed !");
exit(1);
}
printf("Enter eath element:");
for(j = 0; j < length ; j ++)
scanf("%d",ip+j);
max = INT_MIN;
for(sum = j = 0; j < length; j ++)
{
sum += *(ip+j);
if(max < sum)
{
start1 = start2;
max = sum;
}
if(sum < 0){
start2 = j+1;
sum = 0;
}
}
for(j = start1,sum = 0; sum != max; j ++)
sum += *(ip+j);
printf("\nThe subsequence from %d to %d,max sum is %d\n",start1,j-1,max);
return 0;
}
int *ip;
int j,length,max,sum;
int start1 = 0 ,start2 = 0;
printf("Please enter the array's length:");
scanf("%d",&length);
if((ip = (int*)malloc(length*sizeof(int)))==NULL)
{
fprintf(stderr,"Malloc memory failed !");
exit(1);
}
printf("Enter eath element:");
for(j = 0; j < length ; j ++)
scanf("%d",ip+j);
max = INT_MIN;
for(sum = j = 0; j < length; j ++)
{
sum += *(ip+j);
if(max < sum)
{
start1 = start2;
max = sum;
}
if(sum < 0){
start2 = j+1;
sum = 0;
}
}
for(j = start1,sum = 0; sum != max; j ++)
sum += *(ip+j);
printf("\nThe subsequence from %d to %d,max sum is %d\n",start1,j-1,max);
return 0;
}