1. 程式人生 > >求“最大連續段和”題目分析

求“最大連續段和”題目分析

一、題目說明

給出一段長度為n的數列,要求從中找出連續的一段來使得總和最大。輸入包含兩行,第1行表示數列長度為N(N<=100 000),第2行包括N個整數來描述這個數列,每個整數的絕對值不超過1 000。輸出只有一個整數,為最大的連續段總和。

二、輸入輸出樣例

輸入1
5
1 -2 3 1 -4
輸出1
4

三、題目分析

注意:本題所有例項採用從同路徑下in.txt檔案輸入,使用C語言風格輸入輸出。

(一)窮舉法

分析

本題採用窮舉法即將所有可能的連續子序列分別求和取其最大值。

題解示例

#include <cstdio>

int main() {
    FILE *f = fopen("in.txt"
, "r"); int n; fscanf(f, "%d", &n); int data[n + 5], i; for(i = 0; i < n; i++) { fscanf(f, "%d", &data[i]); } int begin, end, sum, max; for(begin = 0; begin < n; begin++) { for(end = n - 1; end >= begin; end--) { for(i = begin
; i <= end; i++) sum += data[i]; if(max < sum) max = sum; sum = 0; } } printf("%d\n", max); return 0; }

注意

題解示例寫法中,有些時候會忘記將每次迴圈後sum值歸零,導致採用輸入示例1的資料輸出9(別問我為什麼會知道orz)。

(二)窮舉法的優化:預先求出從0到n的子序列和

分析

如果提前求出從0到n的子序列和,便可避免很多重複求和消耗時間的問題。把從0到n的子序列和儲存在長度大於n的陣列中,如果需要取某一子序列和,可表示為sum[end] - sum[begin]。

實現

#include <cstdio>
#include <cstring>

int main() {
    FILE *f = fopen("in.txt", "r");

    int n;
    fscanf(f, "%d", &n);
    int data[n + 5], i, sum[n + 5] = {0};
    for(i = 0; i < n; i++) {
        fscanf(f, "%d", &data[i]);
    } 

    int end;
    for(end = 0; end < n; end++) {
        for(i = 0; i <= end; i++) {
            sum[end] += data[i];
        }
    }

    int begin, sum1, max = 0;
    for(begin = 0; begin < n; begin++) {
        for(end = n - 1; end >= begin; end--) {
            sum1 = sum[end] - sum[begin];
            if(max < sum1) max = sum1;
            sum1 = 0;
        }
    }

    printf("%d\n", max);
    return 0;
} 

注意

(1)別忘了初始化sum陣列,而且不要用memset初始化int陣列。memset函式只能用於初始化char陣列(輸出結果沒記錯的話是幾百萬OTZ);
(2)別忘了初始化max,不然會出現(一)中的尷尬情況。

(三)合理運用貪心思想

分析

我們可以得到以下結論:
(1)一個數加上一個正數或0,不小於這個數;
(2)一個數加上一個負數,一定小於這個數。
結合以上結論,我們可以取得以第n個元素為尾(end)的最大子序列和為:
(1)如果第n-1個元素的最大子序列和為負,那麼加上現在這個元素肯定不會大於現在這個元素,則取現在這個元素為第n個元素為尾的最大子序列和;
(2)如果第n-1個元素的最大子序列和為正或0,那麼加上現在這個元素肯定不會小於現在這個元素,則取前面的最大子序列和加上現在元素為第n個元素為尾的最大子序列和。

如果看完上面的文字仍然存在疑惑,我們不妨用一個例子說明:
現在有一家公司,公司之前的經濟狀況已知,現在每天都有人來談生意,可是每天來的人帶來的生意有會虧錢的,也有會賺錢的。
如果今天的生意虧錢,你的公司肯定會比之前虧一些。
如果今天的生意賺錢,你的公司肯定會比之前賺一些。
要是想知道連續幾天帶來生意能產生的最大效益(如果虧錢可以拒絕,但是這樣生意的連續也會停止),那麼截止今天,對於連續虧錢的生意,只虧(或賺)這一筆(回絕這一筆之前的連續最大收益的生意)肯定是效益最大的;對於連續賺錢的生意,把之前賺的算上(接受這一筆之前的連續最大收益的生意)肯定是效益最大的。

運用這種演算法,我們只需要寫一次迴圈(即時間複雜度由O(n2)減到O(n)),就能完成這個求最大的過程。這在競賽中是至關重要的(有的題目資料太多,運用前兩種演算法會超時)。

實現

#include <cstdio>
#include <cstring>

int main() {
    FILE *f = fopen("in.txt", "r");

    int n;
    fscanf(f, "%d", &n);
    int data[n + 5], i;
    for(i = 0; i < n; i++) {
        fscanf(f, "%d", &data[i]);
    } 

    int sum = data[0], max = data[0];
    for(i = 1; i < n; i++) {
        if(sum >= 0) sum += data[i]; else sum = data[i];
        if(sum > max) max = sum;
    } 

    printf("%d\n", max);
    return 0;
} 

注意

本演算法主要的難點在於理解求最大的原理,實現上似乎沒有坑。

四、反思和總結

本題是結合貪心原則和合理運用數學技巧的典型例題,並且能夠反映出迴圈設計、陣列下標使用中的易錯問題,是值得研究的問題。
貪心演算法在以後的學習中會逐步接觸,現在沒有必要急於求成,積累基本演算法知識是現階段最要緊的內容。
(最後坑我最多的還是迴圈啊ORZ)

五、參考閱讀