问题描述:
一个有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;
}
}