常见的LIS 算法有两种解法一种是类动态规划,另一种则是二分法维系一个单调队列。
假设给定数组 d, 求 d 的最长不下降子序列
vector d = {5,6,7,1,2,8}; // 总长度为 6
一:先将dp数组中每个值初始为1
vector dp(d.size(),1); // 按照d的大小初始化并全部赋值为一
二:从末尾倒数第二个元素反向遍历数组 d (因为最后一个元素的最长序列只可能为1)
for (int i = d.size() - 2; i >= 0; i--) {
}
三:j 从 i + 1 遍历到 d.size() - 1, 状态转移
for (int j = i + 1; j < d.size(); j++) {
if (d[i] < d[j]) dp[i] = max(dp[i],dp[j] + 1);
}
四:遍历每一个dp[i] 获取最长不下降子序列长度
int maxn = 1;
for (int i = d.size() - 2; i >= 0; i--) maxn = max(maxn,dp[i]);
总代码:
int LIS(vector d) {
int maxn = 1;
vector dp(d.size(),1);
for (int i = d.size() - 2; i >= 0; i--) {
for (int j = i + 1; j < d.size(); j++) {
if (d[i] < d[j]) dp[i] = max(dp[i],dp[j] + 1);
}
maxn = max(dp[i],maxn);
}
return maxn;
}
从引言推出,对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。
首先我们构造一个单调递增队列 ans = {d[0]};
vector ans;
ans.push_back(d[0]);
注意:ans[i] 为 长度为 i 的 LIS 元素结尾最小值,从引言推出 ans.size() 即为 LIS 的值
然后 i 从第二个元素开始遍历整个数组
for (int i = 1; i < d.size(); i++) {
}
如果 d[i] > ans[ans.size() - 1] // 如果比队列中最大的元素还大,ans 就添加这个元素 ans.push_back(d[i]);
if (ans[ans.size() - 1] < d[i]) ans.push_back(d[i]);
反之,就在单调队列中的找到第一个比 d[i] 大的元素并替换他,实现 ans[i] 为 长度为 i 的 LIS 元素结尾最小值
else {
// upper_bound() 二分找到ans数组中大于d[i]的第一个位置
int pos = upper_bound(ans.begin(),ans.end(),d[i]) - ans.begin();
ans[pos] = d[i];
}
最后ans.size() 就是 LIS 的解
总代码:
int LIS(vector d) {
vector ans;
ans.push_back(d[0]);
for (int i = 1; i < d.size(); i++) {
if (ans[ans.size() - 1] < d[i]) ans.push_back(d[i]);
else {
int pos = upper_bound(ans.begin(),ans.end(),d[i]) - ans.begin();
ans[pos] = d[i];
}
}
return ans.size();
}