最大子段和
例1:下面数列的最大子段和是多少
-2,11,-4,13,-5,-2
概念:给定一个由数字组成的序列,其中连续的一段子序列称为一个子段,子段中的所有数之和称为子段和,这里只考虑非空子段,即至少包含一个元素的子段。
暴力方法
- 1.最暴力的算法,就是枚举两个端点,遍历所选出的子段求和。枚举端点复杂度为 O ( n 2 ) O(n^2) O(n2),求一个子段的和,复杂度为 O ( n ) O(n) O(n),因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)
- 2.求一个子段和可以预处理前缀和进行优化,将这一部分复杂度将为 O ( 1 ) O(1) O(1),总时间复杂度降为 O ( n 2 ) O(n^2) O(n2)
动态规划算法
分析:
- 对于全是非正数的序列,很明显结果就是其中元素的最大值
- 对于有正数的序列,考虑以每一个点为结尾的最大子段和,这个子段一定满足其前缀和为非负,因为如果有一个前缀是负的,那么减掉这个前缀对于这个点一定更优,并且这个子段要尽量向前延伸
实现:
- 所以我们可以使用一次扫描,记录目前统计的 s u m sum sum及答案 a n s ans ans。当 s u m sum sum加上当前位置数如果还是正数就继续累加 s u m sum sum,否则将 s u m sum sum置为0.这样可以舍去所有前缀为负数的情况,并且保证这个子段尽可能长了,每一次 s u m sum sum如果比 a n s ans ans大的话就更新 a n s ans ans,这样就得到了最大子段和
- 时间复杂度为 O ( N ) O(N) O(N)
完整实现:
#include <iostream>
#include <algorithm>
using namespace std;
const int inf = 0x7fffffff;
int num[10];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> num[i];
}
int ans = -inf;
//先标记ans为num数组中最大值
for (int i = 0; i < n; ++i) {
ans = max(ans, num[i]);
}
if (ans <= 0) { //最大值小于0则输出
cout << ans << endl;
} else {
int sum = 0;
for (int i = 0; i < n; ++i) {
//前缀和为负,将sum置为0
if (sum + num[i] < 0) {
sum = 0;
} else { //否则继续累加
sum += num[i];
}
ans = max(ans, sum);
}
}
cout << ans << endl;
return 0;
}
i n p u t input input:
6
-2 11 -4 13 -5 -2
o u t p u t output output:
20
最长上升子序列(LIS)
例2:在序列5,2,7,9,4,5,7,10中,最大上升子序列的长度为
概念:在原序列取任意项,不改变他们在原来数列的先后次序,得到的序列称为原序列的子序列。最长上升子序列,就是给定序列中一个最长的、数值从低到高排列的子排列,最长上升子序列不一定是唯一的。例如:序列2,1,5,3,6,4,6,3的最长上升子序列为1,3,4,6和2,3,4,6,长度均为4
分析:
- 先确定动态规划的状态,这个问题可以用序列某一项作为结尾来作为一个状态。用 d p [ i ] dp[i] dp[i]表示一定以第 i i i项结尾的最长上升子序列。用 a [ i ] a[i] a[i]表示第 i i i项的值,如果有 j < i j < i j<i且 a [ j ] < a [ i ] a[j] < a[i] a[j]<a[i],那么把第i项接到第j项后面构成的子序列长度为 d p [ i ] = d p [ j ] + 1 dp[i] = dp[j] + 1 dp[i]=dp[j]+1
- 要使 d p [ i ] dp[i] dp[i]为以 i i i结尾的最长上升子序列,需要枚举所有满足条件的 j j j。所以状态转移方程为:
dp[i] = max(dp[i], dp[j] + 1), 1 <= j < i && a[j] < a[i]
- 最后, d p dp dp数组中的最大值就是最大上升子序列的长度了
- 时间复杂度为 O ( n 2 ) O(n^2) O(n2)
根据上述状态转移方程可以得到下表:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
a [ i ] a[i] a[i] | 5 | 2 | 7 | 9 | 4 | 5 | 7 | 10 |
d p [ i ] dp[i] dp[i] | 1 | 1 | 2 | 3 | 2 | 3 | 4 | 5 |
完整实现:
#include <iostream>
using namespace std;
int dp[101], a[101], n;
int LIS() {
int ans = 0;
for (int i = 1; i <= n; ++i) {
dp[i] = 1;
for (int j = 1; j < i; ++j) {
if (a[j] < a[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]); //ans记录当前位置的最大上升子序列
}
return ans;
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
cout << LIS() << endl;
return 0;
}
i n p u t input input:
8
5 2 7 9 4 5 7 10
o u t p u t output output:
5
最长公共子序列(LCS)
最长公共子序列:给定两个序列 S 1 S_1 S1和 S 2 S_2 S2,求两者的公共子序列 S 3 S_3 S3的最长的长度
分析:
- 有了前面的基础,可以发现这个问题仍然可以按照序列的长度来划分状态,也就是 S 1 S_1 S1的前 i i i个字符和 S 2 S_2 S2的前 j j j个字符的最长公共子序列长度,记为 l c s [ i ] [ j ] lcs[i][j] lcs[i][j]
- 如果 S 1 S_1 S1的第i项,和 S 2 S_2 S2的第 j j j项相等,那么 S 1 [ i ] S_1[i] S1[i]与 S 2 [ j ] S_2[j] S2[j]作为公共子序列的末尾,则:
lcs[i][j] = lcs[i - 1][j - 1] + 1
- 也可以不让 S 1 [ i ] S_1[i] S1[i]与 S i [ j ] S_i[j] Si[j]作为公共子序列的末尾,则:
lcs[i][j] = max(lcs[i][j - 1], lcs[i - 1][j])
转移方程: l c s [ i ] [ j ] = { l c s [ i − 1 ] [ j − 1 ] + 1 S 1 [ i ] = S 2 [ j ] max { l c s [ i ] [ j − 1 ] , l c s [ i − 1 ] [ j ] } S 1 [ i ] ≠ S 2 [ j ] lcs[i][j] = \begin{cases} lcs[i - 1][j - 1] + 1& S_1[i] = S_2[j] \\ \max\{ lcs[i][j - 1], lcs[i - 1][j]\} & S_1[i] \neq S_2[j] \end{cases} lcs[i][j]={lcs[i−1][j−1]+1max{lcs[i][j−1],lcs[i−1][j]}S1[i]=S2[j]S1[i]̸=S2[j]
举个例子,两个序列 S 1 S_1 S1 = a b c f b c abcfbc abcfbc, S 2 S_2 S2 = a b f c a b abfcab abfcab,根据状态转移方程可得下表:
l c s lcs lcs | 0 | 1( a a a) | 2( b b b) | 3( c c c) | 4( f f f) | 5( b b b) | 6( c c c) |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1( a a a) | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
2( b b b) | 0 | 1 | 2 | 2 | 2 | 2 | 2 |
3( f f f) | 0 | 1 | 2 | 2 | 3 | 3 | 3 |
4( c c c) | 0 | 1 | 2 | 3 | 3 | 3 | 4 |
5( a a a) | 0 | 1 | 2 | 3 | 3 | 3 | 4 |
6( b b b) | 0 | 1 | 2 | 3 | 3 | 4 | 4 |
完整实现:
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
int dp[101][101];
int main() {
string a, b;
cin >> a >> b;
int lena = a.size();
int lenb = b.size();
for (int i = 1; i <= lena; ++i) {
for (int j = 1; j <= lenb; ++j) {
if (a[i - 1] == b[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << dp[lena][lenb] << endl;
return 0;
}
i n p u t input input:
abcdefgh
acjlfabhh
o u t p u t output output:
4