动态规划(Dynamic Programming, DP)为一常用算法思想,本文讲述如何利用DP解决常见的最大字段和及其变种问题。
一、 最大字段和问题
问题定义
设数组为 a [ k ] a[k] a[k], 1 ≤ k ≤ n 1 \le k \le n 1≤k≤n,最大字段和 X X X定义为:
X = max 1 ≤ i ≤ j ≤ n { ∑ k = i j a [ k ] } X=\max_{1\le i \le j \le n} \left \{\sum_{k=i}^j a[k] \right \} X=1≤i≤j≤nmax{k=i∑ja[k]}
X X X直观含义即,求任一连续字数组的最大和。
问题分析
不妨设:
b
[
j
]
=
max
1
≤
m
≤
j
{
∑
k
=
m
j
a
[
k
]
}
b[j]=\max _{1 \le m \le j } \left\{ \sum _{k=m}^j a[k] \right\}
b[j]=1≤m≤jmax{k=m∑ja[k]}
其中,
1
≤
j
≤
n
1 \le j \le n
1≤j≤n
b
[
j
]
b[j]
b[j]的直观含义为,以
a
[
j
]
a[j]
a[j]为结束元素的连续数组的最大和。
由
X
X
X和
b
[
j
]
b[j]
b[j]的定义,易知:
X
=
max
1
≤
j
≤
n
b
[
j
]
X= \max _{1 \le j \le n} b[j]
X=1≤j≤nmaxb[j]
这也很好理解。设想一下,所求得的连续字数组肯定以某个元素结束,求出所有的“以某个元素结束的连续数组最大和
b
[
j
]
b[j]
b[j]”,取其最大的
b
[
j
]
b[j]
b[j],即为
X
X
X。
下面求
b
[
j
]
b[j]
b[j]。
(1) 当
b
[
j
−
1
]
>
0
b[j-1] \gt 0
b[j−1]>0时,无论
a
[
j
]
a[j]
a[j]为何值,
b
[
j
]
=
b
[
j
−
1
]
+
a
[
j
]
b[j] = b[j-1] + a[j]
b[j]=b[j−1]+a[j];
(2)当
b
[
j
−
1
]
≤
0
b[j-1]\le 0
b[j−1]≤0时,无论
a
[
j
]
a[j]
a[j]为何值,
b
[
j
]
=
a
[
j
]
b[j] = a[j]
b[j]=a[j];
例子
| k | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| a[k] | 3 | -4 | 2 | 10 |
| b[k] | 3 | -1 | 2 | 12 |
已知数组
a
[
k
]
a[k]
a[k],求
b
[
j
]
b[j]
b[j],
b
[
j
]
b[j]
b[j]的含义可以参考上面的定义。通过上述给出的算法,可以求得
b
[
j
]
b[j]
b[j]如下。
其中,
b
[
1
]
=
a
[
1
]
b[1] = a[1]
b[1]=a[1],
b
[
2
]
=
b
[
1
]
+
a
[
2
]
b[2] = b[1] + a[2]
b[2]=b[1]+a[2],
b
[
3
]
=
a
[
3
]
b[3] = a[3]
b[3]=a[3],
b
[
4
]
=
b
[
3
]
+
a
[
4
]
b[4] = b[3] + a[4]
b[4]=b[3]+a[4];因此,对数组
a
a
a,最大字段和为
b
[
4
]
b[4]
b[4],即
X
=
12
X = 12
X=12。
代码
求 X X X,即最大字段和的代码。
int b[n + 1];
b[1] = a[1];
int X= a[1];
for(int i = 2; i <= n; i++) {
if(b[i - 1] <= 0) {
b[i] = a[i];
} else {
b[i] = a[i] + b[i - 1];
}
if(b[i] > X)
X = b[i];
}
算法时间复杂度为 O ( n ) O(n) O(n)。
二、变种之一: 两个不重叠(可相邻)连续字数组的最大和
问题定义
设数组
a
[
t
]
a[t]
a[t],
1
≤
t
≤
n
1 \le t \le n
1≤t≤n,两个不重叠连续字数组的最大和
S
S
S定义为:
S
=
max
1
≤
i
≤
j
<
p
≤
q
≤
n
{
∑
t
=
i
j
a
[
t
]
+
∑
t
=
p
q
a
[
t
]
}
S= \max _{1 \le i \le j \lt p \le q \le n} \left\{ \sum _{t=i} ^j a[t] + \sum _{t=p} ^q a[t] \right \}
S=1≤i≤j<p≤q≤nmax{t=i∑ja[t]+t=p∑qa[t]}
问题分析
应用了求最大字段和的方法。其求解算法如下:
(1)从头到尾扫描一遍数组,其循环下标
i
i
i从
1
1
1增加到
n
n
n,依次求得字数组
a
[
1
…
i
]
a[1…i]
a[1…i]的最大字段和,将结果保存在
m
a
x
S
u
m
[
1
…
n
]
maxSum[1…n]
maxSum[1…n]数组;
(2)从尾到头扫描一遍数组,其循环下标
i
i
i从
n
n
n减小到
1
1
1,依次求得字数组
a
[
i
…
n
]
a[i…n]
a[i…n]的最大字段和,将结果保存在
r
M
a
x
S
u
m
[
1
…
n
]
rMaxSum[1…n]
rMaxSum[1…n]数组;
(3)从尾到头扫描一遍数组(其实哪个方向无所谓),其循环下标从
n
−
1
n-1
n−1到
1
1
1,求和
m
a
x
S
u
m
[
i
]
+
r
M
a
x
S
u
m
[
i
+
1
]
maxSum[i] + rMaxSum[i+1]
maxSum[i]+rMaxSum[i+1],取最大的结果,即
max
{
m
a
x
S
u
m
[
i
]
+
r
M
a
x
S
u
m
[
i
+
1
]
}
,
1
≤
i
≤
n
−
1
\max\{ maxSum[i] + rMaxSum[i+1]\},1 \le i \le n-1
max{maxSum[i]+rMaxSum[i+1]},1≤i≤n−1,即为所要求的结果。
例子
| t | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| a[t] | 3 | -4 | 2 | 10 |
| b[t] | 3 | -1 | 2 | 12 |
| maxSum[t] | 3 | 3 | 3 | 12 |
| rb[t] | 11 | 8 | 12 | 10 |
| rMaxSum[t] | 12 | 12 | 12 | 10 |
| 其中, r b rb rb数组与 b b b数组的作用类似,只不过 r b [ j ] rb[j] rb[j]保存的是“从尾到头方向,以 a [ j ] a[j] a[j]元素为结束元素的连续数组的和的最大值”。而 r M a x S u m rMaxSum rMaxSum同样只需根据 r b rb rb数组即可求出。 |
代码
以POJ上面2593题“Max Sequence”为例给出相应的代码。
#include <stdio.h>
const int MAX = 100005;
int arr[MAX];
// 保存字数组的最大字段和,分正向和反向
int maxSeqHere[MAX], rMaxSeqHere[MAX];
// 保存“以某个元素为结束元素的字数组”的最大和,同样分正向和反向
int maxEndingHere[MAX], rMaxEndingHere[MAX];
int main()
{
int n;
while(scanf("%d", &n), n != 0) {
// 初始化
for(int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
// 以下从头到尾扫描数组,求得字数组的最大字段和
maxEndingHere[0] = arr[0];
maxSeqHere[0] = arr[0];
int maxTemp = arr[0];
for(int i = 1; i < n - 1; i++) {
// 利用“问题分析”中b[j]的求法
if(maxEndingHere[i - 1] < 0) {
maxEndingHere[i] = arr[i];
} else {
maxEndingHere[i] = arr[i] + maxEndingHere[i - 1];
}
if(maxEndingHere[i] > maxTemp)
maxTemp = maxEndingHere[i];
maxSeqHere[i] = maxTemp;
}
// 以下从尾到头扫描数组,求得字数组的最大字段和
rMaxEndingHere[n - 1] = arr[n - 1];
rMaxSeqHere[n - 1] = arr[n - 1];
int rMaxTemp = arr[n - 1];
// 保存输出结果
int maxSumOutput = rMaxSeqHere[n - 1] + maxSeqHere[n - 1 - 1];
for(int i = n - 2; i > 0; i--) {
if(rMaxEndingHere[i + 1] < 0) {
rMaxEndingHere[i] = arr[i];
} else {
rMaxEndingHere[i] = arr[i] + rMaxEndingHere[i + 1];
}
if(rMaxEndingHere[i] > rMaxTemp)
rMaxTemp = rMaxEndingHere[i];
rMaxSeqHere[i] = rMaxTemp;
// 直接在反向扫描中求maxSumOutput即可,不用再多一次扫描
if(rMaxSeqHere[i] + maxSeqHere[i - 1] > maxSumOutput)
maxSumOutput = rMaxSeqHere[i] + maxSeqHere[i - 1];
}
printf("%d\n", maxSumOutput);
}
return 0;
}
本文实质是对文章《动态规划求解最大字段和及其变种问题》的排版进行了优化的版本,内容一样,排版更加清晰。

本文介绍如何使用动态规划解决最大字段和问题及两个不重叠连续子数组的最大和问题。通过对数组进行正向和反向扫描,求出任意连续子数组的最大和。采用这种方法能够有效地找到最优解。
655

被折叠的 条评论
为什么被折叠?



