最大子数组和

http://www.ahathinking.com/archives/120.html

一个有N个元素的整型数组arr,有正有负,数组中连续一个或多个元素组成一个子数组,这个数组当然有很多子数组,求子数组之和的最大值。例如:[0,-2,3,5,-1,2]应返回9,[-9,-2,-3,-5,-3]应返回-2。

网上有称之为最大子序列和,亦有称连续子数组最大和。个人觉得叫最大子序列和不太妥,数学上讲,子序列不一定要求连续,而这里我们的题目必然要求是连续的,如果不连续而求子序列最大和很显然就无意义了,这也是为啥又称连续子数组最大和。不过,莫要在意细节。

鉴于《编程之美》对其有几个扩展问题,这里就练手一并实现了,不过整理过程中发现了《编程之美》中的解法错误。


/* DP ultimate version */
int Maxsum_ultimate(int * arr, int size)
{
    int maxSum = -INF;
    int sum = 0;
    for(int i = 0; i < size; ++i)
    {
        if(sum < 0)
        {
            sum = arr[i];
        }else
        {
            sum += arr[i];
        }
        if(sum > maxSum)
        {
            maxSum = sum;
        }
    }
    return maxSum;
}

这个也是2.14的扩展问题,如果数组arr[0],…,arr[n-1]首尾相邻,也就是允许找到一段数字arr[i],…,arr[n-1],arr[0],…,a[j],使其和最大,该如何?

编程之美解法:这个问题的解可以分为两种情况:

1)   解没有跨越arr[n-1]到arr[0] (原问题)

2)  解跨越arr[n-1]到arr[0]

对于第二种情况,只要找到从arr[0]开始和最大的一段(arr[0],…,arr[j])以及以arr[n-1]结尾的和最大的一段(arr[i],…,arr[n-1]【Error 1】,那么第二种情况下和的最大值就为sum=arr[i]+…+arr[n-1]+arr[0]+…arr[j]。如果i<=j,则sum=arr[0]+…arr[n-1]【Error 2】否则sum=arr[0]+…+arr[j]+arr[i]+…arr[n-1]。就是这两个地方,当时觉得有问题。

1)先说Error 1:这里分析的出发点就是错误的,因为“跨越边界的子数组最大和与它两端子数组和是否是最大无关”,前者并非后者的充分条件,这个没有直接的理论证明,举个例子,数组[1,-2,3,5,-1,2]最大子数组和为跨界子数组[1,|  3,5,-1,2],但是[1]并非是以arr[0]开始的最大和;反过来,两端子数组和最大,这个涉及Error 2,如下

2)Error 2:反过来,两端子数组和最大(when i<=j),这个情况复杂多了,远远不是上面所说的那么简单sum= arr[0]+…arr[n-1],例如数组[8,-10,60,3,-1,-6],以arr[0]开始的最大子数组为[8,-10,60,3] ,以arr[n-1]为结尾的最大子数组为[60,3,-1,-6],且i<=j,但是整体的最大子数组却不是arr[0]-arr[n-1],而是跨界子数组[8 | 60,3,-1,-6]。

综上,这个问题不能这样思考,这也给我一个启发:一者看书要试着带有批判性的态度去看;二者别人说的不一定对,或许顺着他的思路觉得有道理,但一定要去亲自实现,实践见真知。

那么怎么做呢?

根据上面两个所举的测试用例,可以发现[1,-2,3,5,-1,2]中最大子数组去掉的是-2,而[8,-10,60,3,-1,-6]中最大子数组排除的是-10,这两个有什么特点?没错,这两个数都是两个数组中“最小”的,所以,类似的,像之前讲过的捞鱼问题,我们找最大子数组的对偶问题——最小子数组,有了最小子数组的值,总值减去它不就可以了么?但是我又想,这个对偶问题只能处理这种跨界的特殊情况吗?答案是肯定的,如果最大子数组跨界,那么剩余的中间那段和就一定是最小的,而且和必然是负的;相反,如果最大子数组不跨界,那么总值减去最小子数组的值就不一定是最大子数组和了,例如上面我们的例子[8-10603-1-6],最大子数组为[8 | 60,3,-1,-6],而最小子数组和为[-10],显然不能用总值减去最小值。

故,在允许数组跨界(首尾相邻)时,最大子数组的和为下面的最大值

Maxsum={ 原问题的最大子数组和;数组所有元素总值-最小子数组和 }


/* 如数组首尾相邻 */
int Maxsum_endtoend(int * arr, int size)
{
    int maxSum_notadj = Maxsum_ultimate(arr,size); /* 不跨界的最大子数组和 */
    if(maxSum_notadj < 0)
    {
        return maxSum_notadj;                      /* 全是负数 */
    }
    int maxSum_adj = -INF;                         /* 跨界的最大子数组和 */
    int totalsum = 0;
    int minsum = INF;
    int tmpmin = 0;
    for(int i = 0; i < size; ++i)     /* 最小子数组和 道理跟最大是一样的 */
    {
        if(tmpmin > 0)
        {
            tmpmin = arr[i];
        }else
        {
            tmpmin += arr[i];
        }
        if(tmpmin < minsum)
        {
            minsum = tmpmin;
        }
        totalsum += arr[i];
    }
    maxSum_adj = totalsum - minsum;
    return maxSum_notadj > maxSum_adj ? maxSum_notadj : maxSum_adj;
}


你可能感兴趣的:(编程,测试,扩展)