最大子列和問題

今天來討論一個很基礎(chǔ)的算法問題阅签,數(shù)列的最大子列和問題。這道題我是在看浙大陳姥姥的Mooc的時候看到的,算是陳越老師作為算法與數(shù)據(jù)結(jié)構(gòu)開篇講解的第一道算法實例題。

那么今天我就來記錄一下分析這道題的過程兄纺。

常用方法

首先,最大子列和這個問題有一個眾所周知的辦法化漆,即為每次從數(shù)列的開頭i,往結(jié)尾N累加钦奋,當(dāng)加至結(jié)尾時座云,由i+1再次累加疙赠,直到N-N。這樣的算法用三個變量三層循環(huán)來分別代表從頭至尾的遍歷朦拖,以及從i - N的前進(jìn)繼續(xù)累加圃阳,最后一層是累加的和。算法可以寫成下面這樣:

    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;
    }

上面的第一種方法應(yīng)該非常好理解璧帝,而由于是三層循環(huán)捍岳,所以,這個算法的時間復(fù)雜度是T(N) = O(N^3)睬隶。這種時間復(fù)雜度的算法锣夹,是非常的低效的,并且我們作為一個有追求的程序員苏潜,看到一個時間復(fù)雜度上有平方以上指數(shù)的银萍,必須要考慮的是降次。

那么其實恤左,第一種算法贴唇,如果我們仔細(xì)思考,那么可以發(fā)現(xiàn)它最里面的一層飞袋,k循環(huán)是一個很愚蠢的行為戳气,因為我們可以直接在第二層循環(huán)里完成累加,于是巧鸭,我們可以寫一個稍微簡單的算法瓶您。

    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;
    }

而這個去了一個循環(huán)的算法,時間復(fù)雜度也一目了然蹄皱,T(N) = O(N^2),但是時間復(fù)雜度依舊還有2次方览闰。接下來還有什么更好的辦法么?

分治法

在這里我們介紹一種方法叫分治法巷折,分而治之压鉴。這個方法的思想是,先把數(shù)列切割成左右兩個部分锻拘,接下來油吭,遞歸的把數(shù)列不斷切割為兩份,直到最小單位為一個元素署拟。而這時婉宰,分別去求他們的子列和,并且在求算左半邊和右半邊的子列和之后推穷,把跨越二分邊界的子列和也求解出來心包。比較左半邊的最大子列和,以及右半邊的最大子列和馒铃,以及跨越邊界的最大子列和蟹腾。取出最大的那個數(shù)痕惋,即為整個數(shù)列的最大子列和。

這是一種很常用的算法思想娃殖,可以先看代碼來理解一下值戳。

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

    /**
     * 分治法主體
     * @param List 求解數(shù)列
     * @param left 左半邊的下標(biāo)
     * @param right 右半邊的下標(biāo)
     * @return 所求數(shù)列的最大子列和
     */ 
    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;
        
        //分解數(shù)列 不斷遞歸調(diào)用
        MaxLeftSum = DivideAndConquer(List, left, center);
        MaxRightSum = DivideAndConquer(List, center + 1, right);
        
        //分別結(jié)算左右兩邊的跨越邊界的和
        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);
    }
    
     /**
     * 取三個數(shù)中的最大值
     * @return int
     */
    private static int Max3(int A, int B, int C) {
        return A > B ? A > C ? A : C : B > C ? B : C;
    }

而分治法的時間復(fù)雜度炉爆,是

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

現(xiàn)在我們可以看到堕虹,這個問題我們已經(jīng)完成我們的降次目標(biāo)了。那么還有更好的算法么芬首?

在線處理

這個問題有個最簡單的算法赴捞,叫在線處理法,遍歷數(shù)列的時候衩辟,順便累加螟炫,每次累加的和若是小于0,那么我們可以認(rèn)為最大子列和為負(fù)數(shù)時艺晴,一定不會讓后面的部分增大了昼钻,所以就可以把它丟棄,重新置當(dāng)前的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;
    }

在線處理的時間復(fù)雜度然评,因為是只遍歷一遍,所以為T(N) = O(N)狈究。
那么說了這么多碗淌,我們需要讓事實來說話,我們現(xiàn)在準(zhǔ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的標(biāo)號,對應(yīng)上面的4種方法磅废,可以看到纳像,如果我們真的用了第一種笨辦法,那么他和高效算法之間的效率差距拯勉,實在是太大了竟趾。

算法的學(xué)習(xí)還要繼續(xù),多看書宫峦,多做題岔帽。那么我們下次再分享了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末导绷,一起剝皮案震驚了整個濱河市犀勒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖贾费,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枚碗,死亡現(xiàn)場離奇詭異,居然都是意外死亡铸本,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門遵堵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箱玷,“玉大人,你說我怎么就攤上這事陌宿∥悖” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵壳坪,是天一觀的道長舶得。 經(jīng)常有香客問我,道長爽蝴,這世上最難降的妖魔是什么沐批? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮蝎亚,結(jié)果婚禮上九孩,老公的妹妹穿的比我還像新娘。我一直安慰自己发框,他們只是感情好躺彬,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梅惯,像睡著了一般宪拥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铣减,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天她君,我揣著相機(jī)與錄音,去河邊找鬼徙歼。 笑死犁河,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的魄梯。 我是一名探鬼主播桨螺,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酿秸!你這毒婦竟也來了灭翔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肝箱,沒想到半個月后哄褒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡煌张,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年呐赡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骏融。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡链嘀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出档玻,到底是詐尸還是另有隱情怀泊,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布误趴,位于F島的核電站霹琼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凉当。R本人自食惡果不足惜枣申,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纤怒。 院中可真熱鬧糯而,春花似錦、人聲如沸泊窘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烘豹。三九已至瓜贾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間携悯,已是汗流浹背祭芦。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留憔鬼,地道東北人龟劲。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像轴或,于是被迫代替她去往敵國和親昌跌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容