题目:输入一个×××数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为 O(n)
        例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,因此输出为该子数组的和18
        如果不考虑时间复杂度,我们可以枚举出所有子数组并求出他们的和。不过非常遗憾的是,由于长度为n的数组有O(n2),具体是n*(n+1)/2个子数组;而且求一个长度为n的数组的和的时间复杂度为O(n)。因此这种思路的时间是O(n3)
 
解题思路:很容易理解,当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。
 
代码:
 
/
//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;
}    

讨论:
·         函数的返回值不是子数组和的最大值,而是一个判断输入是否有效的标志。如果函数返回值的是子数组和的最大值,那么当输入一个空指针是应该返回什么呢?返回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  
解法一改进版:
仔细琢磨就会发现,其实不需要再使用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
解法二:分治算法

跟二分查找的思想相似,我们可以分情况讨论这个问题是不是符合二分查找的条件。
情况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
解法三:

我们试着再观察这个数组的特点,一个元素一个元素的看。
根据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()
{
    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;
}