重新开始战斗15-编程之美-求数组的子数组之和的最大值

问题描述:

一个有N个整数元素的一维数组(A[0],A[1],…,A[n-2],A[n-1]),这个数组当然有很多子数组,那么子数组之和的最大值是什么呢?

 

解法一:

最笨的方法谁都可以想到,枚举所有的子数组之和,找出最大值,代码如下:

Int MaxSum(int* A,int n)

{

         Intmaximum = -INF;

         Intsum;

         For(inti = 0; i < n; i++)

         {

                   sum= 0;

                   for(intj = i; j < n; j++)

                   {

                            sum+= A[j];

                            if(sum> maximum)

                                     maximum= sum;

                   }

         }

         Returnmaximum;

}

可以看出,上面的算法的时间复杂度为O(N2),通常情况下,这样的时间复杂度是不理想的,应该努力将时间复杂度降维O(NlogN),甚至O(N)。

 

解法二:

解法二是书中的解法,不是很容易想到,算法很巧妙,利用了动态规划的方法来解决。

在这里再次提一下什么叫动态规划,书中网上对动态规划的定义有很多,但是再我看来都不够通俗易懂(也许是自己的智商有限吧)。简而言之,动态规划就是通过公式的推导,一步一步从开始得到最后的答案。由于文字功底也不是很好,所以也许有些朋友还是看不懂我在说什么。

一步一步推导?动态规划是递归的反向求解过程。递归是知道A1求A2,一步一步进行,最后求得An,因此在用递归方法进行An求解时,要递归求An-1,一直到A1,A1作为递归的退出条件,然后开始回归,最后得到An的解。

动态规划在我看来是反递归,就是说直接用循环的方法,从A1一步一步求An,所以有些书中就说将递归的过程转化为非递归的方法就是动态规划。

 

回到主题,数组子数组之和最大的情况有哪些?要么包含A[0],要么不包含A[0],或者就是A[0]本身。要判断子数组之和最大是否包含A[0],需要进行下面的计算,时钟包含A[0],计算这种情况下的子数组之和最大值;去掉A[0],计算A[1]到A[n-1]中子数组之和最大值;然后3者A[0],包含A[0]和不包含A[0]进行比较,最大者为子数组之和最大值。

从上面的叙述中,可以发现,问题其实已经转换到求A[1]到A[n-1]中子数组之和最大值,一步一步推进,最后要求A[n-1]到A[n-1]的子数组之和最大值。显然,这个问题可以用递归的方法,以A[n-1]为递归出口,进行递归求解,也可以选择动态规划。这里,书中选择了动态规划进行问题的求解。

 

设A[1],A[2],…,A[n-1]中子数组之和最大为All[1]包含A[1]的和最大为start[1],如果设All[0]为A[0],A[1],…,A[n-1]中子数组之和最大值,那么All[0]=max(A[0],All[1],start[1]+A[0]),要求All[0]就必须求得All[1]和start[1],依次类推:

All[0]=max(A[0],All[1],start[1]+A[0]);

All[1]=max(A[1],All[2],start[2]+A[1]);

All[2]=max(A[2],All[3],start[3]+A[2]);

All[n-2]=max(A[n-2],All[n-1],start[n-1]+A[n-2]);

因此,从A[n-1]即All[n-1]入手,可以求得All[n-2],最后求得All[0]。代码如下:

int max(int x,int y)

{

         Return(x>y) ? x:y;

}

int MaxSum(int* A,int n)

{

         Start[n-1]=A[n-1];

         All[n-1]= A[n-1];

         for(inti = n-2; i >= 0; i--)

         {

                   Start[i]= max(A[i],A[i]+Start[i+1]);

                   All[i]= max(Start[i],All[i+1]);

         }

         returnAll[0];

}

以上的算法时间复杂度为O(N)。不妥的是,上面的算法引入了空间复杂度O(N),然而仔细观察,其实不需要设置数组Start[n]与All[n],因为start[i]与All[i],总是取的较大值,即:

Start = max(A[i],A[i]+Start);

All = max(Start,All);

所以算法改为:

int MaxSum(int* A,int n)

{

         Start=A[n-1];

         All=A[n-1];

         for(inti = n-2; i >= 0; i--)

         {

                   Start= max(A[i],A[i]+Start);

                   All= max(Start[i],All);

         }

         returnAll[0];

}

 

解法三:

解法二很巧妙,应该说很数学,找到了一个推导了规律,然后用动态规划求解,确实对于我来说不容易想到。解法三的思路相对容易想到一些,具体说明如下。

首先,我们要求子数组之和的最大值,那么最开始的想法就是枚举所有的组合,然后求出最大值。从A[0]开始,我们会求(A[0],A[1])组合,(A[0],A[1],A[2])组合,…,直到(A[0],A[1],..A[n-1])组合,然而接着从A[1]开始反复上诉过程。

一定要全部枚举完吗?也许有办法当我们从A[0]累加到A[i]时,发现不用在累加到A[i+1]了,因为之后的累加和一定小于A[0]到A[i]的累加和或者小于以A[j](j>0)为起点的子数组累加和。

这就是出现了负数!

一个数,不管正负,加上一个负数一定是减小的!!!

因此,我们从A[0]开始累加,不断记录子数组之和的最大值,当出现和为负时,即A[0]+A[1]+…+A[i]<0,重新以A[i+1]为起点开始累加,因为,加个负数只能更小。代码如下:

int MaxSum(int* A,int n)

{

         intcurr = 0, maxSum = -INF;

         for(inti = 0; i < n; i++)

         {

                   curr+= A[i];

                   if(curr< 0)

                            curr= 0;

                   if(curr> maxSum)

                            maxSum= curr;

         }

         //如果maxSum为0,可能整个数组全为负,只需找打最大的负值即可

         if(maxSum== 0)

         {

                   for(inti =0; i < n; i++)

                            if(curr>maxSum)

                                     maxSum= curr;

         }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值