最长不下降子序列 转发
DP_最长不下降子序列(LIS)_姬小野的博客-CSDN博客_最长不下降子序列
同类的问题还有“最长上升子序列”, “最长下降子序列”, …
他们的不同就在于定义的core规则不同, 有的是>=, 有的是>, 有的是<
由此启发, 我们可以在解决其他的问题, 不一定是比较数的大小的问题里面抽象出这种模型.
下面介绍这种动态规划入门都会介绍的问题的思路.
首先我们从头开始分析这个问题.
一. 最容易想到的最暴力的方法
对这个序列中的每一个数的”有”和”无”分两种情况讨论. 代码实现上就是递归.
时间复杂度就是O(2^n)
代码实现上较为简单. 不展示
二. 第二种方法是O(n^2)的DP方法
动态规划的问题是无后效性的, 每个问题都可以分解为更小的子问题, 从而求解.
这道题也不例外.
这个序列的每一个数为止都有一个解, 作为子问题的解. 后面的问题的解就是从这些子问题的最优解继承过来的.
so, 给这个序列的解建立数组dp[n], 0 - n分别是截止到Ai的解.
当下一个数要加入来的时候, 有两种情况
前面的数都比当前数更大, 因此以这个数为止的最长不下降子序列的长度就是1. 遍历到第一个数的情况也包含在内.
前面的数有不比当前数大的, 那么这个数的结果dp[i] = max(dp[i], dp[j] + 1). 这个过程遍历前面所有数的dp[j]进行比较.
最后的答案就是所有dp[i]里面的最大值.
这种方法的时间复杂度是O(n^2), 可以看到相比于前面暴力递归的方法有了极大的进步.
代码通过样例, 但不一定能过题, 请谨慎使用.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, x;
vector<int> v;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> x;
v.push_back(x);
}
int dp[(int)v.size()] = {1}, ans = 1;
for (int i = 1; i < (int)v.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (v[i] >= v[j]) dp[i] = max(dp[i], dp[j] + 1); // 状态转移方程
}
ans = max(ans, dp[i]);
}
cout << ans;
}
/*
8
1 2 3 -9 3 9 0 11
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
三. O(nlogn)方法, 维护单调数组
这个方法也是DP方法
时间复杂度可以从O(n^2)降到O(n log n).
我们从最长上升子序列的角度来探讨
假设对一个序列n[1…9] = {2 1 5 3 6 4 8 9 7}, 维护一个单调数组, 使得这个数组为最长上升子序列. 设这个数组为d[ ].
对n[1] = 2, 使得d[1] = 2;
对n[2] = 1, 因为1比2小, 所以修改d[1]的值, 使其为1
对n[3] = 5, 5比1大, 所以len++, d[2] = 5
对n[4] = 3, 3比1大比5小, 所以替换掉5, 使d[2] = 3
对n[5] = 6, 6比d[2]大, 所以len++, d[3] = 6
对n[6] = 4, 4比3大比6小, 所以替换掉6, 使d[3] = 4
对n[7] = 8, 8比4大, 所以len++, 使d[4] = 8
对n[8] = 9, 9比8大, 所以len++, 使d[5] = 9
对n[9] = 7, 7比4大比8小, 所以替换掉8, 使d[4] = 7.
至此这个序列遍历完了, 最小的长度也出来了. 最后的序列是1 3 4 7 9, len = 5
仔细琢磨会觉得, 如最后一步操作, 为什么后面的7反而到他前面的8, 9的前面去了. 其实仔细一想并无问题, 因为即使7出现在前面, 它并不影响最终结果, 因为我们已经得出最后结果就是len, 而以7为结尾的最长序列在该题中是len - 1, 如果后面还有序列, 那么这里把7替换掉8会使得当前状态更优, 因为这样的修改是不会改变当前结果的, 但是确实后续最优状态的基础. 而这道题的动态规划思想就是这样, 不断地获取最优状态.
经过前面的分析我们也许会发现, 这个DP的过程无法存储中间结果, 也就是说我们只能知道最长的子序列是多长, 而无法得到是哪个序列. 可谓是有利有弊.
利用我们维护的数组的单调性, 我们可以用二分法查找这个比当前数更大数的位置, 从而方便的实现替换.
所以时间复杂度为O(n log n).
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
int n, x;
vector<int> v, vec;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> x;
v.push_back(x);
}
for (int i = 0; i < (int)v.size(); ++i) {
if (i == 0) vec.push_back(v[0]);
else {
if (v[i] >= vec[vec.size() - 1]) vec.push_back(v[i]);
else *upper_bound(vec.begin(), vec.end(), v[i]) = v[i];
}
}
for (int i = 0; i < (int)vec.size(); ++i) {
cout << vec[i] << ' ';
}
cout << endl;
cout << "len == " << vec.size() << endl;
}
/*
9
2 1 5 3 6 4 8 9 7
*/
————————————————
版权声明:本文为CSDN博主「姬小野」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wjh2622075127/article/details/80977357