轉自:https://blog.csdn.net/samjustin1/article/details/52043369
問題描述:
連續子序列最大和,其實就是求一個序列中連續的子序列中元素和最大的那個。
比如例如給定序列:
{ -2, 11, -4, 13, -5, -2 }
其最大連續子序列爲{ 11, -4, 13 },最大和爲20。
===============================================================
問題分析:
1.首先最樸素的方法是暴力 O(n^3)
直接兩個for循環枚舉子序列的首尾,然後再來個循環計算序列的和,每次更新和的最大值。
但是這種方法的複雜度是O(n^3),效率實在太低了。。。
————————————————————————————————————————————————
2.第二種方法是預處理 O(n^2)
在讀入的時候將前面數的和放在數組中,就能得到一個數組sum[i]儲存前i個數的和。然後兩重循環枚舉首尾,利用sum數組迅速求出子序列的和。
其實這種方法只是優化了前面那種方法的計算和的循環,複雜的是O(n^2),也很糟糕。
————————————————————————————————————————————————
3.第三種是利用分治思想 O(nlogn)
分治算法但看代碼不是很好理解,其實思想很簡單,就是把序列分成兩塊計算,用遞歸分別求出兩塊序列中的最大子序列和,然後從序列中間向兩邊遍歷求出包含中心的序列的最大和。返回最大的那個序列和。
遞歸真的很神奇,一直把問題分解乘小問題交給下一層遞歸處理,直到最底層。
用分治算法的複雜度好了一些,是O(nlogn),雖然不是最優解,但是理解這種算法的確能讓我們對遞歸理解得更加深刻。
————————————————————————————————————————————————
4.第四種是累積遍歷算法 O(n)
遍歷序列的時候對Sum進行累計,如果Sum累積後小於0的話就把Sum重置爲負無窮,每次更新Sum的最大值。最後便能求出最大值。
其實這個算法就是把序列分爲好幾塊,每一塊滿足:對於任意k,前k個數的和不會小於0(小於0就會分裂成兩塊了),當前i個數的和大於最大值時就進行更新,而最大值的左邊界就是該塊序列的第一個,右邊界是第i個。
時間複雜度爲O(n),而且可以一邊讀取一邊處理,不需要開闢數組來存,空間也很省。
------------------------------------------------------------------------------
舉個例子:
-10 1 2 3 4 -5 -23 3 7 -21 (num)
-10 | 1 3 6 10 8 | -23 | 3 10 | -21 (Sum)(|號爲該處Sum被清零)
由於10是Sum的最大值,所以,紅色的那塊就是要求的範圍了。
------------------------------------------------------------------------------
但是爲什麼所求序列爲什麼是在序列塊中並且是以序列塊第一個數作爲開頭呢?
證明:如果不是這樣的話,會有幾種情況:
1 該目標序列跨越了幾個塊。由於Sum在<=0的時候會重置爲負無窮,如果跨塊的話,沒有重置那它的和肯定不會是最大的。
2 該目標序列在序列塊中間,且目標序列的開頭並不是序列的第一個。由於序列塊前k個數的和不會小於0,所以這樣肯定沒有從第一個開始來的大。
————————————————————————————————————————————————
5.第五種是動態規劃 O(n)
dp做法是很普遍的做法,只要想出狀態轉移方程就可以很快做出來了。
狀態轉移方程:sum[i] = max{sum[i-1]+a[i],a[i]}. (sum[i]記錄以a[i]爲子序列末端的最大連續和。)在dp的過程中便可以更新sum數組的最大值以及兩個邊界。
其實完全可以不用開數組,累計sum直到sum + a < a,把sum賦值爲a,更新最大值就行了。你會發現這跟第4種方法是一樣的。。。只是判斷條件不一樣,一個是sum <= 0一個是sum + a < a。。。(其實是一樣的。。。)所以複雜度和第四種是一樣的都是O(n)。
————————————————————————————————————————————————
6.第六種是第二種累計求和 O(n)
(感謝@shuangde800 大神的指導)
這種方法跟第4種一樣是累計求和。我們可以發現,連續子序列的和其實就是 (到尾端的和)-(到首端-1的和),要讓這個式子最大其實可以讓(到首端-1的和)儘量的小,我們可以遍歷數組,維護(到首端-1的和)的最小值。
理解起來就是遍歷的時候,找出以當前位置爲尾端,以此前的某一點爲首端的 和最大的子序列,然後更新最大值。(其實這個思想和第五種方法是異曲同工的)
由於只需要一遍遍歷,所以複雜度爲O(n)。
===============================================================
僞代碼:
由於下面的Solution裏面我的代碼太挫,於是先貼出僞代碼。
1.暴力 O(n^3):
max←(-∞)
for i←1 to len do
for j←i to len do
sum←0
// 求和
for k←i to j
sum←sum+arr[i]
end for
// 更新最大值
if sum>max then
max←sum
end if
end for
end for
return max
2.預處理 O(n^2):
// 記錄前i項和
sum[0]←0
for i←1 to len do
sum[i]←sum[i-1]+arr[i]
end for
// 枚舉首尾
max←(-∞)
for i←1 to len do
for j←i to len do
// 更新最大值
if sum[j]-sum[i-1]>max then
max←sum[j]-sum[i-1]
end if
end for
end for
return max
3.分治算法 O(nlogn)
function maxsum(arr,left,right)
// 只有一個元素就返回
if right-left=1 then
return
end if
// 劃分[left,mid)和[mid,left)分治
mid←x+(y-x)/2
L←maxsum(arr,left,mid)
R←maxsum(arr,mid,left)
if L>R then
max←L
else
max←R
end if
// 求出處於中間的連續子序列最大和
// 先求從中間向左最大值
tempSum←0
leftSum←arr[m-1]
for i←m-1 downto right do
tempSum←tempSum+arr[i]
if leftSum<tempSum then
leftSum←tempSum
end if
end for
// 再求從中間向右最大值
tempSum←0
rightSum←arr[m]
for i←m to left do
tempSum←tempSum+arr[i]
if rightSum<tempSum then
rightSum←tempSum
end if
end for
midSum←leftSum+rightSum
if max<midSum then
max←midSum
end if
return max
end function
4.累積遍歷算法 O(n)
sum←0
max←arr[0]
for i←1 to len do
if sum<=0 then
sum←a[i]
end if
if max<sum then
max←sum
end if
end for
return max
5.動態規劃 O(n)
sum←(-∞)
max←(-∞)
for i←1 to len do
if sum+arr[i]<arr[i] then
sum←arr[i]
else
sum←sum+arr[i]
end if
if max<sum then
max←sum
end if
end for
return max
6.第二種累計求和 O(n)
max←-INF
recMin←0
for i←1 to len do
sum←sum+arr[i]
temp←sum-recMin
// 更新max
if temp>max then
max←temp
end if
// 更新recMin
if recMin>sum then
recMin←sum
end if
end for
===============================================================
Solution:
hdu這兩題都是要求邊界的。
代碼如下:
hdu 1003:
題意:求出給出序列中和最大的連續子序列,以及該序列的左邊界和右邊界
累積遍歷算法(會TLE)
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: 3.cpp
* Lauguage: C/C++
* Create Date: 2013-08-28 01:12:27
* Descripton: max sum of continuous sequence, simulation
*/
#include <cstdio>
#define rep(i, n) for (int i = 0; i < (n); i++)
const int MAXN = 100100;
int k, a[MAXN];
int main() {
int n;
scanf("%d", &n);
rep(cas, n) {
scanf("%d", &k);
rep(i, k) scanf("%d", &a[i]);
int res = a[0], rl = 0, rr = 0;
int sum = 0, l = 0;
rep(i, k) {
if (sum < 0) {
sum = a[i];
l = i;
}
else
sum += a[i];
if (res < sum) res = sum, rl = l, rr = i;
}
if (cas) printf("\n");
printf("Case %d:\n", cas + 1);
printf("%d %d %d\n", res, rl + 1, rr + 1);
}
return 0;
}
動態規劃
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: 4.cpp
* Lauguage: C/C++
* Create Date: 2013-08-28 15:23:30
* Descripton: max sum of continuous sequence, dp
*/
#include <cstdio>
#define rep(i, n) for (int i = 0; i < (n); i++)
int main() {
int l, R, L, Max, sum, k, t, cas, n;
scanf("%d", &n);
rep(cas, n) {
sum = Max = -0xfffffff;
scanf("%d", &k);
rep (i, k) {
scanf("%d", &t);
if (sum + t < t) sum = t, l = i;
else sum += t;
if (Max < sum) Max = sum, L = l, R = i;
}
if (cas) printf("\n");
printf("Case %d:\n", cas + 1);
printf("%d %d %d\n", Max, L + 1, R + 1);
}
return 0;
}
第二種累計求和
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: 6.cpp
* Lauguage: C/C++
* Create Date: 2013-08-29 00:46:25
* Descripton: o(n)
*/
#include <cstdio>
int n, a[1000000];
int max, recMin, sum, l, recL, recR;
int main() {
int k;
scanf("%d", &k);
for (int cas = 1; cas <= k; cas++) {
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
max = -0xfffffff;
sum = 0;
recMin = 0;
l = -1;
recL = -1, recR = 0;
for (int i = 0; i < n; i++) {
sum += a[i];
if (sum - recMin > max) {
max = sum - recMin;
recL = l;
recR = i;
}
if (recMin > sum) {
recMin = sum;
l = i;
}
}
if (cas - 1) printf("\n");
printf("Case %d:\n%d %d %d\n", cas, max, recL + 2, recR + 1);
}
return 0;
}
————————————————————————————————————————————————
hdu 1231:
題意:求連續子序列最大和及上下界的值,輸出還得處理下
預處理
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: hdu1231.cpp
* Lauguage: C/C++
* Create Date: 2013-08-27 17:06:16
* Descripton: hdu1231, max sum of continuous sequence, pre, O(n^2)
*/
#include <cstdio>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define repf(i, a, b) for (int i = (a); i <= (b); i++)
const int MAXN = 10010;
int k, s[MAXN], a[MAXN];
int main() {
while (scanf("%d", &k) && k) {
s[0] = 0;
repf(i, 1, k) {
scanf("%d", &a[i]);
s[i] = s[i - 1] + a[i];
}
int ans, l, r;
ans = l = r = s[1];
repf(i, 1, k) repf(j, i, k)
if (ans < s[j] - s[i - 1]) {
ans = s[j] - s[i - 1];
l = a[i];
r = a[j];
}
if (ans >= 0)
printf("%d %d %d\n", ans, l, r);
else
printf("0 %d %d\n", a[1], a[k]);
}
return 0;
}
分治思想
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: 2_hdu1231.cpp
* Lauguage: C/C++
* Create Date: 2013-08-27 17:18:49
* Descripton: hdu1231, max sum of continuous sequence, partition
*/
#include <cstdio>
#define rep(i, n) for (int i = 0; i < (n); i++)
const int MAXN = 10010;
struct ANS {
int sum;
int l, r;
ANS() {}
ANS(int a, int b, int c) : sum(a), l(b), r(c) {}
};
ANS maxsum(int* a, int x, int y) {
int m, v, L, R, l, r;
ANS M(a[x], a[x], a[x]), t1, t2;
if (y - x == 1)
return M;
m = x + (y - x) / 2;
t1 = maxsum(a, x, m);
t2 = maxsum(a, m, y);
if (t1.sum >= t2.sum) M = t1;
else M = t2;
v = 0; L = a[m - 1]; l = a[m - 1];
for (int i = m - 1; i >= x; i--){
v += a[i];
if (v > L) L = v, l = a[i];
}
v = 0; R = a[m]; r = a[m];
for (int i = m; i < y; i++) {
v += a[i];
if (v > R) R = v, r = a[i];
}
if (M.sum > (L + R))
return M;
return ANS(L + R, l, r);
}
int k, a[MAXN];
int main() {
while (scanf("%d", &k) && k) {
rep(i, k) scanf("%d", &a[i]);
ANS ans = maxsum(a, 0, k);
if (ans.sum >= 0)
printf("%d %d %d\n", ans.sum, ans.l, ans.r);
else
printf("0 %d %d\n", a[0], a[k - 1]);
}
return 0;
}
累積遍歷算法
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: 3.cpp
* Lauguage: C/C++
* Create Date: 2013-08-28 01:12:27
* Descripton: max sum of continuous sequence, simulation
*/
#include <cstdio>
#define rep(i, n) for (int i = 0; i < (n); i++)
const int MAXN = 10010;
int k, a[MAXN];
int main() {
while (scanf("%d", &k) && k) {
rep(i, k) scanf("%d", &a[i]);
int res = a[0], rl = a[0], rr = a[k - 1];
int sum = 0, l = 0;
rep(i, k) {
if (sum <= 0) {
sum = a[i];
l = a[i];
}
else
sum += a[i];
if (res < sum) res = sum, rl = l, rr = a[i];
}
if (res >= 0)
printf("%d %d %d\n", res, rl, rr);
else
printf("0 %d %d\n", a[0], a[k - 1]);
}
return 0;
}
動態規劃
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: 4.cpp
* Lauguage: C/C++
* Create Date: 2013-08-28 15:11:46
* Descripton: max sum of continuous sequence, dp
*/
#include <cstdio>
#define rep(i, n) for (int i = 0; i < (n); i++)
int main() {
int l, R, L, Max, sum, k, t;
while (scanf("%d", &k) && k) {
sum = Max = -0xfffffff;
rep (i, k) {
scanf("%d", &t);
if (sum + t < t) sum = t, l = t;
else sum += t;
if (Max < sum) Max = sum, L = l, R = t;
}
if (Max >= 0)
printf("%d %d %d\n", Max, L, R);
else
printf("0 %d %d\n", L, t);
}
return 0;
}
第二種累計求和
/*
* Author: illuz <[email protected]>
* Blog: http://blog.csdn.net/hcbbt
* File: 6.cpp
* Lauguage: C/C++
* Create Date: 2013-08-29 00:08:52
* Descripton: o(n)
*/
#include <cstdio>
int n, a[1000000];
int max, recMin, sum, l, recL, recR;
int main() {
while (scanf("%d", &n) && n) {
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
max = -0xfffffff;
sum = 0;
recMin = 0;
l = -1;
recL = -1, recR = 0;
for (int i = 0; i < n; i++) {
sum += a[i];
if (sum - recMin > max) {
max = sum - recMin;
recL = l;
recR = i;
}
if (recMin > sum) {
recMin = sum;
l = i;
}
}
if (max >= 0)
printf("%d %d %d\n", max, a[recL + 1], a[recR]);
else
printf("0 %d %d\n", a[0], a[n - 1]);
}
return 0;
}