最长不下降子序列(Longest Increasing Sequence):在一个数字序列中,找到一个最长的子序列(可以不连续),使得这样的子序列是不下降(即非递减)的。
例如序列 a = {1, 2, 3, -1, -2, 7, 9},它的最长不下降子序列是 {1, 2, 3, 7, 9} 长度为5,{1, 2, 3} 和 {-2, 7, 9} 也是非递减序列但不是最长的。
动态规划求解:dp[i] 表示以 a[i] 结尾的最长不下降子序列的长度。于是对 a[i] 来说就有两种可能:
- 在 a[i] 前存在一个数 a[j](即 j < i ),使得 a[j] <= a[i],那说明 a[i] 可以跟在 a[j] 的后面形成一个新的不降序列,此时,dp[i] 是否要更新则取决于 dp[j] + 1 是否大于 dp[i]。(dp[j] + 1 表示以 a[j] 结尾的LIS加上 a[i] 这个元素后的长度)
- 在 a[i] 前的所有元素都比它大,那么以 a[i] 只能自己形成一个LIS,所以 dp[i] = 1。
所以,以 a[i] 结尾的最长不下降子序列的长度就是上面两种结果中较大的那个。状态转移方式:
d
p
[
i
]
=
m
a
x
(
1
,
d
p
[
j
]
+
1
)
(
j
=
1
,
2
,
⋯
,
i
−
1
a
n
d
a
[
j
]
<
a
[
i
]
)
dp[i] = max \left ( 1, \ dp[j] + 1 \right ) \left ( j = 1,2,\cdots,i-1\ and\ a[j]< a[i] \right )
dp[i]=max(1, dp[j]+1)(j=1,2,⋯,i−1 and a[j]<a[i])
写出代码:
#include <iostream>
#include <vector>
using namespace std;
const int N = 110;
int main() {
int n, a[N], dp[N], ans = -1;
cin >> n;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < n; i++) {
dp[i] = 1; // 初始值为1,即以自己为一个序列
for (int j = 0; j < i; j++) {
if (a[i] >= a[j] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
}
}
ans = max(ans, dp[i]);
}
cout << ans;
return 0;
}
输入:
8
1 2 3 -9 3 9 0 11
1 2 3 3 9 11
输出:
6 // 1 2 3 3 9 11
这里主要问题是得到LIS的长度,但是要做一个好的程序员如果都不想把这个序列输出来,那是真是叫人悲伤。
解决方案:因为可能不止一个序列满足最长,因此用邻接表的方式,声明vector<int> pre[N]
把所有符合条件的序列用前驱指针的方式存起来。vector<int> dest
存储可以形成LIS的终点结点的索引。
之后只需要用 DFS,遍历一下 pre 数组就行了:
void dfs(int u) {
path.push_back(u);
if (pre[u].size() == 0) {
for (int i = path.size() - 1; i >= 0; i--) {
printf("%d", a[path[i]]);
if (i != 0) printf(" ");
}
printf("\n");
path.pop_back(); // 1
return; // 2 这两句不加也可, 为了体现其已走到尽头, 则做return
}
for (auto it : pre[u]) dfs(it);
path.pop_back(); // u的所有孩子都访问完了, 把u从路径数组中移除
}
完整实现:
#include <iostream>
#include <vector>
using namespace std;
const int N = 110;
int n, a[N], dp[N], maxlen = -1, ansIndex = 0;
vector<int> pre[N], path, ans;
void dfs(int u) {
path.push_back(u);
if (pre[u].size() == 0) {
for (int i = path.size() - 1; i >= 0; i--) {
printf("%d", a[path[i]]);
if (i != 0) printf(" ");
}
printf("\n");
path.pop_back(); // 1
return; // 2 这两句不加也可, 为了体现其已走到尽头, 则做return
}
for (auto it : pre[u]) dfs(it);
path.pop_back(); // u的所有孩子都访问完了, 把u从路径数组中移除
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (a[i] >= a[j]) {
if (dp[j] + 1 > dp[i]) { // 比原来的LIS更长, 则i的前驱清空, 将j添加进去
dp[i] = dp[j] + 1;
pre[i].clear();
pre[i].push_back(j);
} else if (dp[j] + 1 == dp[i]) { // 一样长,则j也可作为一种选择, 把j添加进去
pre[i].push_back(j);
}
}
}
if (dp[i] > maxlen) {
maxlen = dp[i];
ans.clear();
ans.push_back(i);
} else if (dp[i] == maxlen) {
ans.push_back(i);
}
}
for (auto it : ans) dfs(it);
return 0;
}
输入:
8
1 2 3 -9 3 9 0 11
输出:(这里只有一个子串满足LIS)
1 2 3 3 9 11