最大子列和问题

今天来讨论一个很基础的算法问题,数列的最大子列和问题。这道题我是在看浙大陈姥姥的Mooc的时候看到的,算是陈越老师作为算法与数据结构开篇讲解的第一道算法实例题。

那么今天我就来记录一下分析这道题的过程。

常用方法

首先,最大子列和这个问题有一个众所周知的办法,即为每次从数列的开头i,往结尾N累加,当加至结尾时,由i+1再次累加,直到N-N。这样的算法用三个变量三层循环来分别代表从头至尾的遍历,以及从i - N的前进继续累加,最后一层是累加的和。算法可以写成下面这样:

    public static int MaxSubseqSum1(int[] A, int N) {
        int ThisSum, MaxSum = 0;
        int i, j, k;
        for (i = 0; i < N; i++) {
            for (j = i; j < N; j++) {
                ThisSum = 0;
                for (k = i; k <= j; k++) {
                    ThisSum += A[k];
                }
                if (ThisSum > MaxSum) {
                    MaxSum = ThisSum;
                }
            }
        }
        return MaxSum;
    }

上面的第一种方法应该非常好理解,而由于是三层循环,所以,这个算法的时间复杂度是T(N) = O(N^3)。这种时间复杂度的算法,是非常的低效的,并且我们作为一个有追求的程序员,看到一个时间复杂度上有平方以上指数的,必须要考虑的是降次。

那么其实,第一种算法,如果我们仔细思考,那么可以发现它最里面的一层,k循环是一个很愚蠢的行为,因为我们可以直接在第二层循环里完成累加,于是,我们可以写一个稍微简单的算法。

    public static int MaxSubseqSum2(int[] A, int N) {
        int ThisSum;
        int MaxSum = 0;
        int i, j;
        for (i = 0; i < N; i++) {
            ThisSum = 0;
            for (j = i; j < N; j++) {
                ThisSum += A[j];
                if (ThisSum > MaxSum) {
                    MaxSum = ThisSum;
                }
            }
        }
        return MaxSum;
    }

而这个去了一个循环的算法,时间复杂度也一目了然,T(N) = O(N^2),但是时间复杂度依旧还有2次方。接下来还有什么更好的办法么?

分治法

在这里我们介绍一种方法叫分治法,分而治之。这个方法的思想是,先把数列切割成左右两个部分,接下来,递归的把数列不断切割为两份,直到最小单位为一个元素。而这时,分别去求他们的子列和,并且在求算左半边和右半边的子列和之后,把跨越二分边界的子列和也求解出来。比较左半边的最大子列和,以及右半边的最大子列和,以及跨越边界的最大子列和。取出最大的那个数,即为整个数列的最大子列和。

这是一种很常用的算法思想,可以先看代码来理解一下。

    /**
     * 分治法,保持API一致
     * @param A 求解数列
     * @param N 元素总数
     * @return
     */
    public static int MaxSubseqSum3(int[] A, int N) {
        return DivideAndConquer(A, 0, N-1);
    }

    /**
     * 分治法主体
     * @param List 求解数列
     * @param left 左半边的下标
     * @param right 右半边的下标
     * @return 所求数列的最大子列和
     */ 
    public static int DivideAndConquer(int[] List, int left, int right) {
        int MaxLeftSum, MaxRightSum;
        int MaxLeftBorderSum, MaxRightBorderSum;
        int LeftBorderSum, RightBorderSum;
        int center, i;

        if (left == right) {
            if (List[left] > 0) {
                return List[left];
            } else {
                return 0;
            }
        }

        center = (left + right) / 2;
        
        //分解数列 不断递归调用
        MaxLeftSum = DivideAndConquer(List, left, center);
        MaxRightSum = DivideAndConquer(List, center + 1, right);
        
        //分别结算左右两边的跨越边界的和
        MaxLeftBorderSum = 0; LeftBorderSum = 0;
        for (i = center; i >= left; i--) {
            LeftBorderSum += List[i];
            if (LeftBorderSum > MaxLeftBorderSum) {
                MaxLeftBorderSum = LeftBorderSum;
            }
        }

        MaxRightBorderSum = 0; RightBorderSum = 0;
        for (i = center + 1; i <= right; i++) {
            RightBorderSum += List[i];
            if (RightBorderSum > MaxLeftBorderSum) {
                MaxRightBorderSum = RightBorderSum;
            }
        }
        return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum);
    }
    
     /**
     * 取三个数中的最大值
     * @return int
     */
    private static int Max3(int A, int B, int C) {
        return A > B ? A > C ? A : C : B > C ? B : C;
    }

而分治法的时间复杂度,是

T(1) = O(1)
T (N) = 2T(N/2) + cN
= 2 [2T( N/22 ) + cN/2] + cN
= 2kO(1) + ckN 
= O(NlogN )

现在我们可以看到,这个问题我们已经完成我们的降次目标了。那么还有更好的算法么?

在线处理

这个问题有个最简单的算法,叫在线处理法,遍历数列的时候,顺便累加,每次累加的和若是小于0,那么我们可以认为最大子列和为负数时,一定不会让后面的部分增大了,所以就可以把它丢弃,重新置当前的sum = 0

代码如下:

    public static int MaxSubseqSum4(int[] A, int N) {
        int ThisSum, MaxSum;
        int i;
        ThisSum = MaxSum = 0;
        for (i = 0; i < N; i++) {
            ThisSum += A[i];
            if (ThisSum > MaxSum) {
                MaxSum = ThisSum;
            } else if (ThisSum < 0) {
                ThisSum = 0;
            }
        }
        return MaxSum;
    }

在线处理的时间复杂度,因为是只遍历一遍,所以为T(N) = O(N)。
那么说了这么多,我们需要让事实来说话,我们现在准备一个30个元素的队列,让每个算法跑100000次来观察所需时间。

    public static void main(String[] args) {
        int size = 31;
        int[] testArr = {3, -1, 5, 10, -8, 2, 1, 4, 0, 7, -5, -6, 3, -8, -10,
                10, -20, -8, 0, 3, 0, -9, -10, 5, 3, 0, -8, 10, -4, 10, -7};
        int runCount = 100000;
        testFunction1(testArr, size, runCount);
        testFunction2(testArr, size, runCount);
        testFunction3(testArr, size, runCount);
        testFunction4(testArr, size, runCount);
    }

最后的log输出是这样的:

Max Subsequence Sum 1 is 23
function1 run time is  738ms
Max Subsequence Sum 2 is 23
function2 run time is  44ms
Max Subsequence Sum 3 is 17
function3 run time is  110ms
Max Subsequence Sum 4 is 17
function4 run time is  54ms

上面的function的标号,对应上面的4种方法,可以看到,如果我们真的用了第一种笨办法,那么他和高效算法之间的效率差距,实在是太大了。

算法的学习还要继续,多看书,多做题。那么我们下次再分享了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容

  • PTA 中国大学MOOC-陈越、何钦铭-数据结构 01-复杂度1 最大子列和问题(20 分) 给定K个整数组成的序...
    FesonX阅读 637评论 0 0
  • 题目描述: 给定KK个整数组成的序列{ N_1N​1​​, N_2N​2​​, ..., N_KN​K​​ },“...
    板混DK阅读 309评论 0 0
  • 2014年3月,公司组织去越南旅游。行程一共是四天,前两天在美奈,后两天在胡志明市。当时去美奈玩的中国人还很少,据...
    献刀阅读 306评论 0 1
  • 感赏儿子照顾弟弟,没让弟弟乱跑没让弟弟单独坐电梯,对弟弟有责任心。 感赏儿子有好吃的惦记着妈妈,有...
    风之飘逸aa阅读 159评论 0 1
  • 和朋友去看了《从你的全世界路过》。在这部电影里,我感受到了亲情,友情,爱情,感受到了很多点点滴滴的微妙情感,当然其...
    舒柠檬阅读 467评论 0 1