最长不下降子序列是一道非常经典的题目,我们假设题目如下:

有一个数组 $ a $,现在要从中抽取一个严格上升的子序列(子序列就是你可以从原序列里删除一些数,保留一部分数得到的新数列)(严格上升也就是不能相等只能递增),现在要求出这个子序列最长有多长?

举例来说,假设有一个数组 a = [10, 9, 2, 5, 3, 7, 101, 18]。这个数组的最长上升子序列是 [2, 3, 7, 101],这是从原数组中第3, 5, 6, 7个位置选取得到的,长度为4。(当然最后一个数字换成 18 也可以)

平方做法

通常来讲,平方做法很容易想到,我们开一个数组 $ f $ ,用 $ f_i $ 表示以 $ a_i $ 结尾的 LIS 的长度。我们从左往右计算每一个 $ f_i $ ,每一个 $ f_i $ 可能由 \(f_1, f_2...f_{i-1}\)得到。比方说假如 $ a_i > a_j $,那么 \(a_i\) 就可以接到 $ f_{i-1} $ 表示的序列后面,长度加一,枚举所有之前的 f,取最大值,最终就可以计算出来。

#include <vector>
#include <algorithm>
using namespace std; int longestIncreasingSubsequence(const vector<int>& a) {
int n = a.size();
vector<int> f(n, 1); // 初始化每个元素的LIS长度为1
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (a[i] > a[j]) {
f[i] = max(f[i], f[j] + 1);
}
}
}
return *max_element(f.begin(), f.end()); // 返回f中的最大值,即最长上升子序列的长度
} int main() {
vector<int> a = {10, 9, 2, 5, 3, 7, 101, 18};
int result = longestIncreasingSubsequence(a);
return result;
}

N log N 做法

更高效的方法是通过维护一个数组 $ tail $ 来实现,其中 $ tail_i $ 存储长度为 i 的所有上升子序列中末尾最小的元素(比如 $ tail_4 $ 表示所有长度为 4 的 LIS 里最后一个数字最小能达到多少)。在更新过程中,我们使用二分搜索来确定一个元素应当放置在 $ tail $ 中的位置。

我们还是从左向右来枚举,对于每个元素 $ a_i $,在 \(tail\) 中找到第一个大于 $ a_i $ 的位置。

  • 如果找到这样的位置,即表示之前有一个序列的末尾可以被替换,所以修改其 $ tail $ 值为新 $ a_i $
  • 如果没有找到,说明 $ a_i $ 比之前所有数字都大,那么把 $ a_i $ 追加到 $ tail $ 末尾,最长的上升序列长度成功加1.

对于 $ tail $ 来说,下标越大,条件越难满足,则能达到的最小值越大,所以 $ tail $ 显然是严格递增的,这不难理解,而每一次操作也维持了 $ tail $ 的单调性。

举例来说,现在\(tail = [3, 5, 6, 19, 25]\),而当前 $ a_i $ 为 8,可以找到 $ tail_4 = 19$ 这表明在我之前出现过一个长度为4,以 19 结尾的序列,以及一个长度为3,以小于8结尾的序列,那么这个长度为3的序列可以接上8,替换掉原来的19,一定更优

#include <vector>
#include <algorithm>
using namespace std; int longestIncreasingSubsequence(const vector<int>& a) {
vector<int> tail;
for (int x : a) {
auto it = lower_bound(tail.begin(), tail.end(), x);
if (it == tail.end()) {
tail.push_back(x); // 如果x大于tail中所有元素,追加到末尾
} else {
*it = x; // 替换为更小的值,保持序列最小
}
}
return tail.size(); // tail的大小就是最长上升子序列的长度
} int main() {
vector<int> a = {10, 9, 2, 5, 3, 7, 101, 18};
int result = longestIncreasingSubsequence(a);
return result;
}

获得具体的数列

除了得到最长长度,此方法也是支持把序列获取到的,增加一个数组,每一次更新 tail 时,来记录每个元素的前一个元素的索引,这里附加上 chatgpt 的代码:

def longest_non_decreasing_subsequence(arr):
from bisect import bisect_right if not arr:
return 0, [] dp = [] # 存放各长度最长不下降子序列的最小结尾元素的值
predecessor = [-1] * len(arr) # 前驱元素的索引
indices = [] # 存放各长度最长不下降子序列的最小结尾元素的索引 for i, x in enumerate(arr):
pos = bisect_right([arr[idx] for idx in indices], x)
if pos < len(indices):
indices[pos] = i
predecessor[i] = indices[pos-1] if pos > 0 else -1
else:
indices.append(i)
predecessor[i] = indices[-2] if len(indices) > 1 else -1 # 回溯找到最长不下降子序列
n = len(indices)
sequence = [0] * n
k = indices[-1]
for j in range(n-1, -1, -1):
sequence[j] = arr[k]
k = predecessor[k] return n, sequence # 示例
arr = [10, 9, 2, 5, 3, 7, 101, 18]
length, sequence = longest_non_decreasing_subsequence(arr)
print("Length:", length) # 输出长度
print("Sequence:", sequence) # 输出序列

严格上升和不下降

要从求最长严格上升子序列变为求最长不下降子序列(也就是允许序列中的元素相等),主要修改的部分就是二分查找部分。在严格上升的场景中,tail 不会出现相等值,我们寻找的是第一个大于等于当前元素的位置,而在不下降的场景中,要找到的是第一个大于当前元素的位置。

举例子来说,当要求变为严格不下降,$ tail $ 就有可能出现相等值,假设 $ tail = [1, 2, 2, 3, 3, 3, 5] $,且下一个 \(a_i\) 为 2。那么应该更新 \(a_2\) 第一个3。而扩展 $ tail $ 的条件也放宽为大于等于,具体代码就不再展示。

NlogN 求最长不下降子序列(LIS)的更多相关文章

  1. P1020 导弹拦截(nlogn求最长不下降子序列)

    题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达捕捉到敌国的导弹 ...

  2. SPOJ 3943 - Nested Dolls 最长不下降子序列LIS(二分写法)

    现在n(<=20000)个俄罗斯套娃,每个都有宽度wi和高度hi(均小于10000),要求w1<w2并且h1<h2的时候才可以合并,问最少能剩几个. [LIS]乍一看跟[这题]类似, ...

  3. 算法进阶 (LIS变形) 固定长度截取求最长不下降子序列【动态规划】【树状数组】

    先学习下LIS最长上升子序列 ​ 看了大佬的文章OTZ:最长上升子序列 (LIS) 详解+例题模板 (全),其中包含普通O(n)算法*和以LIS长度及末尾元素成立数组的普通O(nlogn)算法,当然还 ...

  4. JDOJ 1946 求最长不下降子序列个数

    Description 设有一个整数的序列:b1,b2,…,bn,对于下标i1<i2<…<im,若有bi1≤bi2≤…≤bim 则称存在一个长度为m的不下降序列. 现在有n个数,请你 ...

  5. HDU 1025 Constructing Roads In JGShining's Kingdom[动态规划/nlogn求最长非递减子序列]

    Constructing Roads In JGShining's Kingdom Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65 ...

  6. 求最长不下降子序列(nlogn)

    最长递增子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增子序列. 设dp[i]表示以i为结尾的最长 ...

  7. HDU 1087 最长不下降子序列 LIS DP

    Nowadays, a kind of chess game called “Super Jumping! Jumping! Jumping!” is very popular in HDU. May ...

  8. Monkey and Banana(dp,求最长的下降子序列)

    A group of researchers are designing an experiment to test the IQ of a monkey. They will hang a bana ...

  9. nlogn的最长不下降子序列【tyvj1254挑选士兵】

    var a,d:Array[-..]of longint; i,n,m,k,l:longint; function erfen(x:longint):longint; var mid,h,t:long ...

  10. tyvj 1049 最长不下降子序列 n^2/nlogn

    P1049 最长不下降子序列 时间: 1000ms / 空间: 131072KiB / Java类名: Main 描述 求最长不下降子序列的长度 输入格式 第一行为n,表示n个数第二行n个数 输出格式 ...

随机推荐

  1. 如何在无窗口模式下为git的tag和commit操作加GPG私钥——如何在命令行模式下使用gpg秘钥为git操作签名

    相关: 如何在无窗口模式下运行GPG--如何在命令行模式下使用gpg生成秘钥:How to make gpg prompt for passphrase on CLI--GPG prompt for ...

  2. 强化学习运行环境,atari 2600 游戏模拟器,atari-py库 —— 无法运行游戏,pacman,surround,报错: Segmentation fault (core dumped)

    atari2600运行环境: https://github.com/openai/atari-py 安装环境,以及导入 rom文件这里不进行介绍(前文已介绍): 测试游戏环境rom文件能否正常加载如内 ...

  3. 高性能无锁队列 Disruptor 核心原理分析及其在i主题业务中的应用

    作者:来自 vivo 互联网服务器团队- Li Wanghong 本文首先介绍了 Disruptor 高性能内存队列的基本概念.使用 Demo.高性能原理及源码分析,最后通过两个例子介绍了 Disru ...

  4. Maven经验分享(一)安装部署 转

    Maven安装部署 1.安装 在安装Maven之前,首先要确认你已经正确安装了JDK.Maven可以运行在JDK 1.4及以上的版本上.本书的所有样例都基于JDK 5及以上版本 目录下的安装包,直接a ...

  5. MySql 字段类型长度问题理解

    mysql中字段长度理解 字符长度 设计表中设置的是字符长度,任意字符都占一个字符长度,使用char_length 函数获取 char_length(`name`) 字节长度 字节长度和数据表的字符集 ...

  6. 玄机蓝队靶场_应急响应_02:apache日志分析

    日志分析这块,感觉都是对grep.awk.sort.wc.uniq,这几个命令的使用. 一:靶场 (1)直接cd到linux日志, cd /var/log 发现apache2目录, cd ./apac ...

  7. 聊一聊 C# 中让人惶恐的 Bitmap

    一:背景 1. 讲故事 在.NET高级调试的旅程中,我常常会与 Bitmap 短兵相接,它最大的一个危害就是会让程序抛出匪夷所思的 OutOfMemoryException,也常常会让一些.NET开发 ...

  8. html 跳转到新的网址

    更新window.location.href后面的值即可 文件名为 index.html <!DOCTYPE html> <html> <head> <met ...

  9. 又一个Rust练手项目-wssh(SSH over Websocket Client)

    原文地址https://blog.fanscore.cn/a/61/ 1. wssh 1.1 开发背景 公司内部的发布系统提供一个连接到k8s pod的web终端,可以在网页中连接到k8s pod内. ...

  10. 【YashanDB知识库】离线升级一章22.2不支持直接升级到23.1

    [标题]离线升级一章22.2不支持直接升级到23.1 [问题分类]文档问题 [关键词]YashanDB, 离线升级, 版本兼容 [问题描述]文档中提到22.2版本不支持直接升级到23.1. [问题原因 ...