编程珠玑:求子数组最大和

问题描述:

输入一个整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。当所有的输入都是负数时,总和最大的子数组是空数组,总和为0。例如输入数组包含下面10个元素:

 

那么该程序的输出为x[2...6]的总和,即187.

题外话:

有人说编程珠玑这本书是经典,我也认同,但有人也说这本书是让人费脑筋提前掉头发的书,我更认同,有句话我想说,书中很多地方的描述非常绕,绕到你很难理解,即便理解了也很难给人讲明白。该问题就是很好的一个例子。下面首先用书中的思路解决该问题,再用另外一种思路解决,其实两种解决办法是完全一样的,但思路真的千差万别,孰优孰劣,读者自鉴。

Pearl解题思路:

假设我们已解决了x[0...i-1]的问题,即我们找到了x[0...i-1]中最大子数组和,那么只需要扩展到x[i]就可以解决这个问题了。根据类似于分治算法的原理:前i个元素中,最大总和子数组要么在前i-1个元素中(我们将其存储在maxsofar中),要么其结束位置为i(我们将其存储在maxendinghere中)。

Pearl代码实现

int FindMaxSumOfVectorPearl(int *pData, int nLength)
{
    int maxsofar = 0;      /* max sum of the vector*/
    int maxendinghere = 0; /* candidate max sum of the vector */

    if(NULL == pData || nLength <= 0)
    {
        return 0;
    }

    for(int i = 0; i < nLength; i++)
    {
        /* invariant: maxsofar and maxendinghere are accurate for x[0...i-1]*/
        maxendinghere = max(maxendinghere + pData[i], 0);
        maxsofar = max(maxsofar, maxendinghere);
    }

    return maxsofar;
}
     理解为个程序的关键就在于变量maxendinghere。在循环中的第一个赋值语句之前,maxendinghere是结束位置为i-1的最大子数组的和;赋值语句将其修改为结束位置为i的最大子数组的和。若加上x[i]之后结果依然为正值,则该赋值语句使maxendinghere增大x[i];若加上x[i]之后结果为负值,该赋值语句就将maxendinghere重新设为0(因为结束位置为i的最大子数组现在为空数组)。

国人解题思路:

    当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。

国人代码实现:

int FindMaxSumOfVectorMine(int *pData, int nLength)
{
    int maxsofar = 0;      /* max sum of the vector*/
    int maxendinghere = 0; /* candidate max sum of the vector */

    if(NULL == pData || nLength <= 0)
    {
        return 0;
    }

    for(int i = 0; i < nLength; i++)
    {
        maxendinghere += pData[i];
        if(maxendinghere <= 0)
        {
            maxendinghere = 0;
            continue;
        }

        /* if a greater sum is found, update the greatest sum */
        if(maxendinghere > maxsofar)
        {
            maxsofar = maxendinghere;
        }
    }

    return maxsofar;
}
     我认为下面的思路更好懂一些,上面的代码更简短一些。

总结

     这个题目中涉及了一个很重要的算法设计技术:保存状态,避免重复计算。比如本题,最笨的办法或许是枚举出所有子数组并求出它们的和,这种思路增加了太多的重复计算,时间复杂度为O(n3)。引申一下,在求Fibonacci数列的问题中,递归算法一直做为教材中经典案例,但是这个算法也有重复计算的问题,该算法的改良版正是着力解决了重复计算而使算法更加高效。求n的阶乘也同样适用。


你可能感兴趣的:(数据结构,算法,面试题,编程珠玑,c/c++)