三种算法求解一个数组的子数组最大和

三种算法求解一个数组的子数组最大和

题目:要求一个数组连续下标和的最大值,数组的元素可正、可负、可为零,例如-2,5,3,-6,4,-8,6将返回8。

这题是很经典的一道面试题,也有各种解法,从算法分析上,时间复杂度也有很大差别,下面我就给出三种不同的解法。

方法一:暴力枚举法

此种方法最简单,我想应该也是每个人拿到题目想到的第一种解法了,学过一点编程的人都应该能编出此类程序。
记sum[i..j]为数组中第i个元素到第j个元素的和(其中0<=i<j<=n-1),通过遍历所有的组合之和,就能找到最大的一个和了。
伪代码如下:
int maxSubArray(int *A,int n) {
    int maxium = -INF; //保存最大子数组之和
    for i=0 to n-1 do 
        sum = 0; //sum记录第i到j的元素之和
        for j=i to n-1 do
            sum += A[j];
        if sum>maxium do //更新最大值 
            maxium = sum; 
    return maxium;
}
此种方法的时间 复杂度为O(n2),显然不是一种很好的办法,也不是公司面试希望你写出这样的程序的。

方法二:分支界定
这里再介绍一种更高效的算法,时间 复杂度为O(nlogn)。这是个分治的思想,解决复杂问题我们经常使用的一种思维方法——分而治之。
而对于此题,我们把数组A[1..n]分成两个相等大小的块:A[1..n/2]和A[n/2+1..n],最大的子数组只可能出现在三种情况:
    A[1..n]的最大子数组和A[1..n/2]最大子数组相同;
    A[1..n]的最大子数组和A[n/2+1..n]最大子数组相同;
    A[1..n]的最大子数组跨过A[1..n/2]和A[n/2+1..n]
前两种情况的求法和整体的求法是一样的,因此递归求得。
第三种,我们可以采取的方法也比较简单,沿着第n/2向左搜索,直到左边界,找到最大的和maxleft,以及沿着第n/2+1向右搜索找到最大和maxright,那么总的最大和就是maxleft+maxright。
而数组A的最大子数组和就是这三种情况中最大的一个。
伪代码如下:
int maxSubArray(int *A,int l,int r) {
    if l<r do 
        mid = (l+r)/2;
        ml = maxSubArray(A,l,mid); //分治 
        mr = maxSubArray(A,mid+1,r);
        for i=mid downto l do 
            search maxleft; 
        for i=mid+1 to r do 
            search maxright; 
        return max(ml,mr,maxleft+maxright); //归并 
        then //递归出口 
            return A[l]; 
}
方法三:动态规划
这算是一个经典的动态规划的题目了,如果不知道动态规划可以先不去理解这个名词。用通俗点的语言描述这个算法就是:
令cursum(i)表示数组下标以i为起点的最大连续下标最大的和,而maxsum(i)表示前i个元素的最大子数组之和。那么我们就可以推出下一个maxsum(i+1)应该为cursum(i+1)和maxsum(i)中选取一个最大值。递推式为:
cursum(i) = max{A[i],cursum(i-1)+A[i]};
maxsum(i) = max{maxsum(i-1),cursum(i+1)};
伪代码为:
int maxSubArray(int *A,int n) { 
    cursum = A[0]; 
    maxsum = A[0];
    for i=1 to n-1 do
        /*当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。*/ 
        if cursum<0 do 
            cursum = 0;
        cursum += A[i]; 
        if cursum>maxsum do
            maxsum = cursum; 
    return maxsum; 
}
这种算法时间复杂度只是O(n),效果非常好!
类似理解:

设sum[i]为以第i个元素结尾且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i] = max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

伪代码如下

复制代码
result = a[1]
sum = a[1]

for i: 2 to LENGTH[a]
  if sum > 0
    sum += a[i]
  else
    sum = a[i]

  if sum > result
    result = sum

return result

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值