最长子序列问题详解

article/2025/9/28 10:41:37

提到最长子序列问题,想必大家都不陌生,今天我主要想分享一下我对子序列问题的一些理解:

先拿最长上升子序列问题来说吧:

很明显这是一个动态规化问题,仔细想想也不难得出其状态转移方程

首先介绍一下dp[]数组的含义:dp[i]表示以第i个数结尾的最长上升子序列长度,接下来就是状态转移的具体方法了

就是每更新到i时,就用dp[1~i-1]更新dp[i]的值,当j<i且a[j]<a[i]时,dp[i]=max(dp[i],dp[j]+1),这样我们就用n^2的复杂度处理完了这个问题

下面给出一道例题及其代码:

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式

第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式

输出一个整数,表示最大长度。

数据范围

1≤N≤1000,
−10^9≤数列中的数≤10^9

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4
#include<bits/stdc++.h>
using namespace std;
int a[1002],dp[1002];
int main()
{int n;cin>>n;int ans=0;for(int i=1;i<=n;i++){cin>>a[i];dp[i]=1;for(int j=1;j<i;j++)if(a[i]>a[j])//只有当待更新位置的元素大小大于之前元素的大小时才能被更新dp[i]=max(dp[i],dp[j]+1);ans=max(ans,dp[i]);}printf("%d",ans);return 0;
}

下面再给出一道题,看看这道题又应该怎么做呢?

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式

第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式

输出一个整数,表示最大长度。

数据范围

1≤N≤100000,
−10^9≤数列中的数≤10^9

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

可能就会有同学问了,这两个题除了数据有一点不同外其余没什么差别啊,我还用原来的方法不就行了么?
分析完数据范围就可以发现,原来的方法的复杂度并不能解决当前这个问题,我们需要找到更优的思路,下面我将给出nlogn的思路:
我们不妨先拿出一个栈,这个栈用来存放最长子序列,栈里面的元素是严格单调递增的,那我们应该怎样更新栈里面的元素呢?
做法应该是这样的:
首先要知道栈顶存放的是栈中最大的元素,当遇到一个比栈顶元素还大的元素时,我们没有理由不把他放入栈中,这时候最长子序列的数目就会增加1,可当发现一个比栈顶元素要小的元素呢?
仅仅不更新栈顶就可以了么?当然不是啦,我们要替换第一个非严格大于他的栈内元素,由于我们所构造的栈本来就是非严格单调递增的,所以也就是找到第一个大于等于我们当前所思考元素的栈内元素并将其替换
仔细想想这是为什么?我们栈内被替换的元素被替换前后是不是发挥着一样的作用,被替换后答案序列长度并没有减少,反而更有利于接下来栈内元素的增多。
由于我们维护的栈是有序的,所以我们可以用二分查找,所以复杂度变成了nlogn,下面给出代码:

#include<bits/stdc++.h>
using namespace std;
#define INF 999999999
int a[100002],dp[100002];
int main()
{int n;cin>>n;memset(dp,0x3f,sizeof(dp)); for(int i=1;i<=n;i++){cin>>a[i];*lower_bound(dp+1,dp+n+1,a[i])=a[i];//lower_bound是二分查找函数,当然也可以手写二分}printf("%d\n",lower_bound(dp+1,dp+n+1,INF)-dp-1);return 0; 
}

下面我将对常见的最长子序列问题进行总结:(以下思路复杂度均为nlogn)

最长上升子序列 每次更新第一个大于或者等于当前元素的栈内元素(维护的是单调递增栈)

最长不下降子序列 每次更新第一个大于当前元素的栈内元素(维护的是单调递增栈)

最长下降子序列 每次更新第一个小于或者等于当前元素的栈内元素(维护的是单调递减栈)

最长不上升子序列 每次更新第一个小于当前元素的栈内元素(维护的是单调递减栈)

我们求一个大于或者大于等于当前元素的栈内元素时可以利用upper_bound函数和lower_bound函数,但要注意的是
初始时一定要把数组中的所有数都置为最大值
那我们要求一个小于或者小于等于当前元素的栈内元素时又应该怎么办呢?这时候我们就要用一个技巧了,
不难发现如果一个序列是a1,a2,……an,而另一个序列为an,an-1,……a1,那么前一个序列的最长下降子序列就变成了
后一个序列的最长上升子序列,而前一个序列的最长不上升子序列就变为了后一个序列的最长不下降子序列。
这样我们就又可以利用现成的函数了,而不需要手写二分啦(我真是个小天才哈哈).

现在给出一个Dilworth定理推论:

能覆盖整个序列的最少的不上升子序列的个数”等价于“该序列的最长上升子序列长度

能覆盖整个序列的最少的不下降子序列的个数”等价于“该序列的最长下降子序列长度

下面给出一个相应例题及其代码:

题意:某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

共一行,输入导弹依次飞来的高度。

输出格式

第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围

雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。

输入样例:

389 207 155 300 299 170 158 65

输出样例:

6
2
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1002;
int a[N],dp1[N],dp2[N],h[N];
int main()
{int n=0,x;while(scanf("%d",&x)!=EOF) a[++n]=x;memset(dp1,0x3f,sizeof(dp1));memset(dp2,0x3f,sizeof(dp2));for(int i=n;i>=1;i--)//求最长不上升子序列,可以转化为逆序求最长不下降子序列 *upper_bound(dp1,dp1+n,a[i])=a[i];printf("%d\n",lower_bound(dp1,dp1+n,0x3f3f3f3f)-dp1);for(int i=1;i<=n;i++)*lower_bound(dp2,dp2+n,a[i])=a[i];printf("%d",lower_bound(dp2,dp2+n,0x3f3f3f3f)-dp2);return 0;
}

写到这大家有没有一个疑问,我们刚才利用单调栈只能求出最长子序列的长度,那我们要是求最长子序列是什么呢?在朴素版的求解最长子序列问题里面,我们可以设置一个pre[i]来表示第i个数前一个数是什么,就这样求可以利用while循环来求出最长序列是什么,下面附上相应代码,最长子序列存在st数组中,最长子序列的最后一个元素是ans.

tt=0;
while(ans)
{st[++tt]=ans;ans=pre[ans];
}

那利用单调栈求解最长子序列问题时应该怎么求解最长子序列是什么这个问题呢?

其实还是利用刚才那个贪心的思想,我们这个时候另开一个id数组,id[i]记录第i个数位于最长子序列的哪个位置,虽然第i个数并不一定在最终的最长子序列中,但是他肯定在求解最终最长子序列过程中出现过,我们只要把他在求解过程中出现的位置记录下来就行了,由于贪心的思路是从前往后的,那么也就是说同是位于最长子序列中第k个位置的两个数,谁的位置靠后,谁的字典序就更偏小,这是显然的,要不然前边的也不会被后面的数替换了,所以我们求最长子序列时应该从后往前求,遍历到某个位置,能加就加进去,比如说我们现在遍历到了位置i,已经找到了最长子序列中的最后三个数,最长子序列一共有n个数,那么我们下一个要找的就是位于最长子序列第n-3个位置上的数,也就是id[i]==n-3,如果id[i]>n-3,此时我们应该直接忽略,原因就是我们已经取到了更优的位于最长子序列上第id[i]个位置上的数,而如果id[i]<n-3,我们同样也不能取,原因是因为我们还没有找到最长子序列第n-3个位置上的数,所以不能取在这个位置之前的数,也就是说我们此时只能取id[i]=n-3的i。

举个例子(以最长上升子序列为例):

1 3 5 4 6 2 7 8 6

 最后最长上升子序列的长度为6,但并不是ans里面的数,我们来模拟求一下最长上升子序列的数是多少,先从后往前找第6个,id[9]!=6,继续看下一个,发现id[8]等于6,说明最长上升子序列第6个数是原序列第8个数8,接着找第5个,id[7]=5,说明最长上升子序列第5个数是原序列第7个数7,接着找第4个,id[6]!=4,id[5]=4,说明最长上升子序列第4个是原序列第5个数6,然后找第三个,id[4]=3,说明最长上升子序列第3个数是原序列第4个数4,然后找第二个数,id[3]=3,不符合,继续向前找,id[2]=2,说明最长上升子序列第2个数是原序列第2个数3,然后找第一个,id[1]=1,说明原序列中第一个数就是最长上升子序列中第一个数,就这样我们就找完了最长上升子序列中所有的数,依次是 1 3 4 6 7 8

下面给出对应代码,大家可以结合代码好好理解一下过程:

其中ans中存的是最后遍历结束后的最长子序列(并不是结果),st数组中保留最后的结果,也就是对应的最长子序列在原数组中的位置,id_len是id数组的长度

ans.push_back(s[1]);
int id_len=0;id[++id_len]=1;
for(int i=2;i<=tt;i++)
{if(s[i]>ans[ans.size()-1]) {ans.push_back(s[i]);id[++id_len]=ans.size();//记录第i个数在目前最长子序列中的位置continue;}int t=lower_bound(ans.begin(),ans.end(),s[i])-ans.begin();ans[t]=s[i];//替换id[++id_len]=t+1;//记录第i个数在目前最长子序列中的位置
}

输出过程:

for(int i=ans.size(),j=id_len;i>=1;j--)
{if(id[j]==i) st[++tt]=j,i--;
}

得到st数组后,我们只需要倒着输出即可输出最长上升子序列,再次需要注意的是st数组中存的只是位置,需要用对应的数组去索引

以上就是常见的最长子序列问题啦,如果大家有更好的方法处理这些问题,欢迎大家评论!


http://chatgpt.dhexx.cn/article/xvJN2Mr0.shtml

相关文章

如何查看pip版本

Windows系统如何查看pip版本 直接运行pip show pip

python之pip版本问题

我们在默认安装python软件之后&#xff0c;其自行安装的pip的版本可能比较落后。我们使用pip安装模块的时候经常提示安装失败&#xff0c;查看安装失败原因&#xff0c;部分是因为pip不是最新版本需要升级版本才能继续安装&#xff0c;因此写下一点小心得&#xff0c;让我们都能…

pip 查看可安装版本

pip 查看可安装版本 pip版本&#xff1a; pip -V pip 20.1.1 from /usr/lib/python2.7/site-packages/pip (python 2.7) 查看pip可安装版本 import pip._internal.utils.compatibility_tags print(pip._internal.utils.compatibility_tags.get_supported())

python如何查看pip版本并且升级pip

第一次写Python的学习经历 我之前也安装过Python,今天&#xff0c;终于重新安装了64位的windows的Python,于是在命令行输入&#xff1a;“pip list”出现以下的提示&#xff1a; 这个提示以前也出现过&#xff0c;但是看不懂&#xff0c;也不知道怎么处理&#xff0c;然后又胡…

python pip 查找指定安装包版本信息

一、windows操作系统 pip list | findstr 查找的包&#xff08;支持模糊查询&#xff09;例&#xff1a; pip list | findstr huawei结果&#xff1a; 二、linux操作系统 pip list | grep 查找的包&#xff08;支持模糊查询&#xff09;

更新pip版本

更新pip版本 从报错来看是因为pip版本为20.2.3&#xff0c;但当前可用版本是21.0.1 python -m ensurepippython -m pip install --upgrade pippip list设置镜像默认源 #临时使用 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package #设置默认源 pip inst…

拉取的docker镜像中更换python以及pip版本

场景&#xff1a; 需要tensorflow1.10 python3.6 的环境&#xff0c;但是拉取的tf1.10镜像中&#xff0c;默认python版本是3.5 &#xff0c;此时我们需要安装python3.6版本&#xff0c;将python命令的软连接删除&#xff0c;并且指向新安装的3.6&#xff0c;且pip的连接也应该…

Python查看所有版本及pip

1、查看python默认版本&#xff1a; python --version &#xff08;默认的版本我改为了python3&#xff0c;所以结果是3.7的&#xff09; 2、查看另外安装的2.7的版本&#xff1a; python2.7 --version 3、查看python安装路径&#xff1a; 1&#xff09;终端输入python2.7回…

python降低pip版本

场景&#xff1a; 使用Pycharm开发时&#xff0c;经常会遇到因为pip版本过高而导致下载第三方库失败的情况 解决方案&#xff1a; 经查资料&#xff0c;发现可以通过降低pip的版本来解决问题 如下&#xff1a; 第一种方法&#xff1a;使用Terminal命令行进行降级 python -m pip…

Linux查看pip版本号的命令,pip 经常使用命令及控制台怎么查看python 及pip 和已安装包版本号...

在使用python的时候&#xff0c;常常使用到pip这个工具&#xff0c;能够很方便的线上安装依赖库&#xff0c;固然pip还有不少参数均可以帮咱们去查询一些库信息&#xff0c;在安装python的时候&#xff0c;下载带有pip的安装包就能够直接安装pip啦&#xff0c;固然没有带pip的&…

查看pip版本 及升级pip命令报错解决办法

pip install pytest_ordering时警告&#xff0c;pip版本过低 WARNING: You are using pip version 21.1.3; however, version 21.2.4 is available. You should consider upgrading 1、查看pip&#xff1a; pip show pip 2、升级pip&#xff1a;python -m pip install --upg…

最新升级pip命令,查看pip版本命令,pycharm升级pip命令,推荐收藏关注不迷路

1. 查看pip版本命令&#xff1a;pip -V &#xff0c;可以看看以下代码运行完过后&#xff0c;自己的pip版本有没有升级成功 python升级pip命令&#xff1a;打开命令行输入 python -m pip install -U pip 回车 pycharm升级pip命令 &#xff1a;打开命令行输入&#xff1a; py…

[Python]pip查找包的历史版本

pip查找包的历史版本 场景&#xff1a;在一些时候通过pip install xxx 安装第三方库的时候默认情况下安装最新版本&#xff0c;由于是最新版本有个稳定性就不得不考虑其中&#xff0c;所以部分场景会存在一些bug这就要求我们安装历史版本&#xff0c;对一些更新频率比较高的三…

已解决正确查看pip版本、查看pip帮助命令

已解决&#xff08;pip查看版本报错&#xff09;Usage&#xff1a;pip [options] no such option: —verson 文章目录 报错代码报错翻译报错原因解决方法千人全栈VIP答疑群联系博主帮忙解决报错 报错代码 粉丝群一个小伙伴想用pip查看版本报错&#xff0c;但是发生了报错&#…

解决OpenSSL SSL_read: Connection was reset, errno 10054

报错问题&#xff1a; fatal: unable to access ‘https://github.com/Sunyt1992/ref-comet.git/’: OpenSSL SSL_read: Connection was reset, errno 10054 OpenSSL SSL_read:连接已重置&#xff0c;错误号10054 字面意思&#xff1a;服务器的SSL证书灭有经过第三方机构的签署…

TCP 10054

2019独角兽企业重金招聘Python工程师标准>>> TcpSocket:Connection reset by peer 10054 10054 ConnectionReset 客户端网络情况不好的条件下&#xff0c;比如正在下载其他东西&#xff0c;而服务器又有心跳超时处理&#xff0c;客户端的心跳迟迟发不上去&#xff0…

gitHub报错10054、443解决办法

为了学习netty框架&#xff0c;将netty的源码fork到自己的github账号下&#xff0c;使用IDEA进行下载&#xff0c;报了errno 10054的错误&#xff0c;如下所示&#xff1a; 21:40:24.692: [..\..\javaworkspace] git -c credential.helper -c core.quotepathfalse -c log.show…

git 提交10054

环境&#xff1a;goland 远端&#xff1a;github 在当前路径下执行&#xff1a; git config --global core.longpaths true

telnetlib备份cisco交换机10054

问题 想用python的telnetlib模块对所有交换机做个自动备份&#xff0c;但是有几台3850交换机汇报如下错误: buf self.sock.recv(50) ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。调试了一下午终于在一篇文章中找到了临时解决办法&#xff0…