版权声明:本文为博主原创文章,转载请注明出处-- https://blog.csdn.net/qq_38790716/article/details/88076052
最大子段和
例1:下面数列的最大子段和是多少
-2,11,-4,13,-5,-2
概念:给定一个由数字组成的序列,其中连续的一段子序列称为一个子段,子段中的所有数之和称为子段和,这里只考虑非空子段,即至少包含一个元素的子段。
暴力方法
- 1.最暴力的算法,就是枚举两个端点,遍历所选出的子段求和。枚举端点复杂度为 ,求一个子段的和,复杂度为 ,因此时间复杂度为
- 2.求一个子段和可以预处理前缀和进行优化,将这一部分复杂度将为 ,总时间复杂度降为
动态规划算法
分析:
- 对于全是非正数的序列,很明显结果就是其中元素的最大值
- 对于有正数的序列,考虑以每一个点为结尾的最大子段和,这个子段一定满足其前缀和为非负,因为如果有一个前缀是负的,那么减掉这个前缀对于这个点一定更优,并且这个子段要尽量向前延伸
实现:
- 所以我们可以使用一次扫描,记录目前统计的 及答案 。当 加上当前位置数如果还是正数就继续累加 ,否则将 置为0.这样可以舍去所有前缀为负数的情况,并且保证这个子段尽可能长了,每一次 如果比 大的话就更新 ,这样就得到了最大子段和
- 时间复杂度为
完整实现:
#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;
}
:
6
-2 11 -4 13 -5 -2
:
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
分析:
- 先确定动态规划的状态,这个问题可以用序列某一项作为结尾来作为一个状态。用 表示一定以第 项结尾的最长上升子序列。用 表示第 项的值,如果有 且 ,那么把第i项接到第j项后面构成的子序列长度为
- 要使 为以 结尾的最长上升子序列,需要枚举所有满足条件的 。所以状态转移方程为:
dp[i] = max(dp[i], dp[j] + 1), 1 <= j < i && a[j] < a[i]
- 最后, 数组中的最大值就是最大上升子序列的长度了
- 时间复杂度为
根据上述状态转移方程可以得到下表:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
5 | 2 | 7 | 9 | 4 | 5 | 7 | 10 | |
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;
}
:
8
5 2 7 9 4 5 7 10
:
5
最长公共子序列(LCS)
最长公共子序列:给定两个序列 和 ,求两者的公共子序列 的最长的长度
分析:
- 有了前面的基础,可以发现这个问题仍然可以按照序列的长度来划分状态,也就是 的前 个字符和 的前 个字符的最长公共子序列长度,记为
- 如果 的第i项,和 的第 项相等,那么 与 作为公共子序列的末尾,则:
lcs[i][j] = lcs[i - 1][j - 1] + 1
- 也可以不让 与 作为公共子序列的末尾,则:
lcs[i][j] = max(lcs[i][j - 1], lcs[i - 1][j])
转移方程:
举个例子,两个序列 = , = ,根据状态转移方程可得下表:
0 | 1( ) | 2( ) | 3( ) | 4( ) | 5( ) | 6( ) | |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1( ) | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
2( ) | 0 | 1 | 2 | 2 | 2 | 2 | 2 |
3( ) | 0 | 1 | 2 | 2 | 3 | 3 | 3 |
4( ) | 0 | 1 | 2 | 3 | 3 | 3 | 4 |
5( ) | 0 | 1 | 2 | 3 | 3 | 3 | 4 |
6( ) | 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;
}
:
abcdefgh
acjlfabhh
:
4