求“最大連續段和”題目分析
一、題目說明
給出一段長度為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)