最长上升子序列问题(LIS问题)与最长不上升子序列问题的四种方法(c++ 模板代码)

news2024/12/23 15:37:58

文章目录

  • 动态规划
  • 树状数组
  • 线段树
  • 二分查找

最大上升子序列问题也叫做LIS问题,与最大公共子序列LCS问题是一类经典问题,在本章我们将总结一下求解LIS最大上升子序列几种方法,同时也会给出对应的最大不上升子序列的求解方法。 关于LCS问题,我在后面会再出一篇博客来讲解,

废话不多说,我们直接进入正题,如果你还一点都不了解LIS问题,那么请不要看这篇博客,本篇博客只是对于LIS的求解的总结与归纳,但凡是涉及结论公式求证的我一概不会论证,其实是我不会 ,在这里我将会直接使用


最大上升子序列: [4,2,3,6,9] 是一个序列,那么显而易见他的LIS应该是 [2,3,6,9],长度为4吗,注意LIS问题是可以不连续的,我这个例子是连续的。

动态规划

我们定义dp[i] 作为以i结尾的子序列的最大上升子序列的长度

因此,我们可以知道,如果我们要求dp[i],必须首先对i之前位置的元素 j 进行逐一遍历,然后和i位置的元素比较,只要j位置的元素小于i位置的元素,那么就是一个满足条件的元素,注意整个过程中需要满足
j < i , a [ j ] < a [ i ] j<i ,a[j]<a[i] j<ia[j]<a[i]
那么我们便可以通过来求得dp[i]
d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i]=max(dp[i],dp[j]+1) dp[i]=max(dp[i],dp[j]+1)
因为如果找到了一个符合条件的 a[j]<a[i] ,那么j这个位置的元素就可以是dp[i]的一个最大上升子序列的元素,因此dp[j]+1 将此长度加1,由于dp是一个状态转移的过程,我们还要确保之前的值与当前求得最长的长度要取得一个最大值

代码如下:

/*
	最长上升子序列
	*/
	for (int i = 1; i <= n; i++)
	{	
		dpAdd[i] = 1;	//以每一个i位置开始的元素的默认最长上升子序列长度是1(该元素本身)
		for (int j = 1; j < i; j++)	//遍历所有比i位置小的元素
		{
			if (a[j] < a[i])	//查找前面的位置的元素是否小于i位置
			{
				//存在一个比i位置小的元素在j位置,则记录j位置dp要加1,表示在j位置的后面可以找到一个i位置,使得i位置的元素比j位置大,i和j满足一个上升的子序列,所以取dp[j]+1 和 它本身的dp[i]的较大者
				dpAdd[i] = max(dpAdd[i], dpAdd[j] + 1);
			}
		}
		//dp[i]更新为[0,i]区间内的最长上升子序列的最大长度
		res = max(res, dpAdd[i]);
	}

求解最大不上升子序列的问题也是如此:
最大不上升子序列指的是当前元素的后面的所有元素都小于等于当前元素,即
i < j , a [ i ] > = a [ j ] i<j, a[i]>=a[j] i<ja[i]>=a[j]
它的dp状态转移方程是一样的,因此我们便可以通过将遍历顺序倒过来,来求解最大不上升子序列问题。

/*
	最长不上升子序列
	*/
	for (int i = n; i >= 1; i--)
	{
		dpNAdd[i] = 1;
		for (int j = i + 1; j <= n; j++)	//在i的后面的元素开始
		{
			if (b[j] <= b[i])	//i后面的要小于等于i,所以才是不上升的
			{
				dpNAdd[i] = max(dpNAdd[i], dpNAdd[j] + 1);
			}
		}
		ans = max(ans, dpNAdd[i]);
	}

可见,在用DP方法来求解LIS问题中,我们用到了两重循环,因此在最差的情况下我们的时间复杂度为 O ( N 2 ) O(N^2) O(N2)

另外我们可以发现,我们在DP的过程中,注意到在转移的过程中,我们需要反复地从 i - i中找满足条件的 j的位置,并且寻找最大的 a[j],那么我们可以把这个过程抽象为一个从区间中寻找最大值的过程,那么我们便可以使用树状数组或者线段树来对这个过程进行维护。


树状数组

我们使用树状数组来查询 1-i 中的区间的最大值,这里我简单画了一个树状数组的实现的过程:
在这里插入图片描述
需要注意的点:

  1. 在查询最大上升子序列的时候,由于前面的元素j不能等于 i位置,所以我们干脆就查询 a[i]-1,这样就做到了在 i之前查询
  2. 在查询最大不上升子序列的时候,我们就把遍历的顺序从后往前,这样就做到了查询了i之后的元素,同时是小于等于的,直接a[i]即可。
  3. 每次查询的时候都需要最后加上1,因为还要包含其自身

为什么可以使用树状数组?

我认为如上图所示,树状数组每次查询的时候都是查询所有比它小的位置的元素,而这些位置对于i来说都是 j<i 的,所以说只要在查询的时候获得一个最大值,即获得了以i结尾的最大不上升子序列的长度。

代码如下:

int tree[N];		
inline int lowbit(int i)
{
	return i & (-i);
}
void update(int i,int val)
{
	while (i <= maxn)
	{
		tree[i] = max(tree[i], val);
		i += lowbit(i);
	}
}
int query(int i)
{
	int sum = 0;
	while (i > 0)
	{
		sum = max(sum, tree[i]);
		i -= lowbit(i);
	}
	return sum;
}

int main()
{
	/*
	最长上升子序列
	*/
	maxn = 9;	//数组的元素最大值
	for (int i = 1; i <= n; i++)
	{
		int num = query(a[i] - 1) + 1;
		/*
		相当于for (int j=1;j<i;j++) if (a[j]<a[i])
			查询以a[i]结尾的最长上升子序列的最大长度,注意:查询时不包含a[i]元素,所以长度要再加1
		*/
		update(a[i], num);//维护树状数组,即维护了各个位置元素的最大上升子序列的长度
		res = max(res, num);
	}
	cout << res << endl << endl;

	memset(tree, 0, sizeof(tree));
	/*
	最长不上升子序列
	*/
	for (int i = n; i >= 1; i--)
	{
		//要查询的是不上升,所以后面的小于等于前面的,所以说查询时包括b[i],无须向上面一样减1
		int num = query(b[i]) + 1;	

		update(b[i], num);
		ans = max(ans, num);
	}
	cout << ans;
	return 0;
}


树状数组的查询是 O ( N l o g N ) O(NlogN) O(NlogN)


线段树

线段树和树状数组的解法相同,只不过线段树代码量多,但是线段树易懂,树状数组比较抽象

代码如下:

/*
线段树
*/
int add[N];

inline void push_up(int i)
{
	//上移,维护区间最大值
	tree[i] = max(tree[i << 1], tree[i << 1 | 1]);
}
void build(int i, int pl, int pr)
{
	if (pl == pr)
	{
		tree[i] = 0;
		return;
	}
	int mid = (pl + pr) >> 1;
	build(i << 1, pl, mid);
	build(i << 1 | 1, mid + 1, pr);
	push_up(i);
}
void update(int i, int pl, int pr, int x, int val)
{
	if (pl == pr)
	{
		tree[i] = max(tree[i], val);
		return;
	}
	int mid = (pl + pr) >> 1;
	if (x <= mid) update(i << 1, pl, mid, x,val);	//递归左子树
	if (x > mid) update(i << 1 | 1, mid + 1, pr, x, val);//递归右子树
	push_up(i);
}
int query(int i, int pl, int pr, int L, int R)
{
	if (L > R) return 0;
	if (L <= pl && pr <= R)
	{
		return tree[i];
	}
	int ans = 0;
	int mid = (pl + pr) >> 1;
	if (L <= mid) ans =max(ans,query(i << 1, pl, mid, L, R));
	if (R > mid) ans =max(ans, query(i << 1 | 1, mid + 1, pr, L, R));
	return ans;
}

int main3()
{
	build(1, 1, n);
	/*
	最大上升子序列
	*/
	for (int i = 1; i <= n; i++)
	{
		//查询以i结尾的最大上升子序列长度的最大值,是小于号,所以说要减1
		int num = query(1, 1, n, 1, a[i] - 1) + 1;	

		update(1, 1, n, a[i], num);
		res = max(res, num);
	}
	cout << res << endl << endl;
	return 0;
}

线段树的时间复杂度也是 O ( N l o g N ) O(NlogN) O(NlogN)


二分查找

关于二分查找的详细的解释:
二分查找的详细解释

  • 最长上升子序列:

    • 如果最后一个元素小于下一个元素,则满足
    • 否则在升序序列中查找第一个大于等于x的元素,然后替换
  • 最长不上升子序列

    • 如果最后一个元素大于等于下一个元素,则满足
    • 否则在降序序列中查找第一个大于x的元素,然后替换
/*
二分查找
*/

int d1[N], d2[N];
int main()
{
	int len1 = 1, len2 = 1;
	d1[1] = d2[1] = a[1];
	for (int i = 2; i <= n; i++)
	{
		//最长上升子序列,最后一个元素一定小于下一个元素
		if (d1[len1] < a[i]) d1[++len1] = a[i];
		else *lower_bound(d1 + 1, d1 + 1 + len1, a[i]) = a[i];
		//最长不上升子序列,最后一个元素一定大于等于下一个元素
		if (d2[len2] >= a[i]) d2[++len2] = a[i];
		else *upper_bound(d2 + 1, d2 + 1 + len2, a[i], greater<int>()) = a[i];
	}
	cout << len1 << " " << len2 << endl;

	return 0;
}

时间复杂度 O ( N l o g N ) O(NlogN) ONlogN

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【表格单元格可编辑】vue-elementul简单实现table表格点击单元格可编辑,点击单元格变成输入框修改数据

前言 这是最近遇到的功能&#xff0c;经常会需要一个表格可以编辑数据 类似于excel那种点击一下单元格就可以编辑数据&#xff0c;修改后鼠标移动出去 光标消失就会保存数据给后台 这里记录一下实现方法&#xff0c;其实也比较简单 就是通过角标来判断显示隐藏的 效果图 代码…

[Android开发基础4] 点击事件的响应与处理

文章目录 方法一&#xff1a;控件的onClick属性 方法二&#xff1a;内部类 方法一&#xff1a;控件的onClick属性 利用控件自带的onClick属性&#xff0c;指定事件处理函数名称即可实现控件点击事件的处理 这里有个小技巧就是当设置完控件的onClick属性后&#xff0c;它会报没…

XXE漏洞常见利用点总结

目录 知识点小结 常用payload 本地文件读取 SSRF 引入外部实体 dtd 信息探测 XXE漏洞攻击 案例演示 案例一&#xff08;有回显&#xff09; 案例二&#xff08;无回显读取本地敏感文件(Blind OOB XXE)&#xff09; XXE 防御 使用语言中推荐的禁用外部实体的方法 知…

08-linux网络管理-iftop命令详解

文章目录1. 安装2. 基本使用2.1 命令2.2 输出2.3 说明3. 选项3.1 选项说明3.2 几个示例-n&#xff08;不查找主机名&#xff09;-i &#xff08;查看指定网卡流量&#xff09;-P&#xff08;显示主机端口&#xff09;-t&#xff08;不使用ncurses 界面&#xff09;4. ncurses界…

IDEA集成Docker插件实现一键自动打包部署

一. 概述 大家部署项目的时候&#xff0c;动辄十几个服务&#xff0c;每次修改逐一部署繁琐不说也会浪费越来越多时间&#xff0c;所以本篇整理通过一次性配置实现一键部署微服务&#xff0c;直接上教程。 二. 配置服务器 1、Docker安装 服务器需要安装Docker&#xff0c;如…

iOS单元测试怎么写 ?

iOS单元测试怎么写 ? 什么是单元测试 ? 针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。对于面向对象编程&#xff0c;最小单元就是方法 iOS 集成了自己的测试框架 OCUnit 和 UITests 为什么单元测试 ? 执行单元测试&#x…

网络基础2-3 ---传输层的UDP协议:DP的特点,UDP的协议格式,UDP的应用

目录 一、tcpdump命令 二、UDP协议 前言 2.1、UDP协议的特点&#xff1a; 2.2、UDP的协议格式 16位的UDP长度&#xff1a; 结合抓包工具&#xff1a;分析一下UDP协议&#xff0c;就利用我们之前写的udp_socket编程 16位的校验和&#xff1a; 2.3、UDP的应用&#xff1…

Python的序列结构及常用操作方法,学完这一篇你就彻底懂了

上一篇&#xff1a;Python流程控制语句之跳转语句 文章目录前言一、索引二、切片三、序列相加四、乘法五、检查某个元素是否是序列的成员六、计算序列的长度、最大值和最小值总结前言 序列是一块用于存放多个值的连续内存空间&#xff0c;并且按一定顺序排列&#xff0c;每个值…

【论文阅读】Cleanits: A Data Cleaning System for Industrial Time Series

论文来源 标题: Cleanits (Xiaoou Ding,2019) 作者: Xiaoou Ding, Hongzhi Wang, Jiaxuan Su, Zijue Li, Jianzhong Li, Hong Gao 期刊: Proceedings of the VLDB Endowment 研究问题 工业时间序列数据清洗系统 1&#xff09;缺失值插补&#xff0c;2&#xff09;匹配不一致…

Maven可选依赖与排除依赖

可选依赖——指的是对外隐藏当前所依赖的资源&#xff08;不透明&#xff09; 可选依赖的作用&#xff1a;是隐藏所使用的依赖&#xff0c;用于控制当前依赖资源能否被别人发现 可选依赖的含义&#xff1a;当前工程所依赖的资源&#xff0c;不被其他项目所调用此依赖 pom文件…

DolphinScheduler 3.1.0 海豚集群运维使用问题记录

文章目录海豚常见问题1. 认证问题2. 时区问题3. jdk问题导致的认证问题4. 海豚调度sqoop任务问题(1. 海豚不允许脚本有空行出现(2. 脚本调脚本:权限不足(3. 直接执行某个表的sqoop同步任务:(4. sudo权限不足5. 海豚配置hive/impala数据源问题(1.海豚连接hive数据源配置(2. 配置…

[架构之路-97]:《软件架构设计:程序员向架构师转型必备》-7-需求分析与业务需求领域建模

前言&#xff1a;需求分析工程师工作中业务领域&#xff0c;而业务领域有很多业务领域专有的概念&#xff1b;程序员主要工作在计算机领域&#xff0c;他们没有足够的业务领域的知识识别业务领域的过于专业化的业务需求。为了确保业务需求能够被软件工程师正确无误地实现&#…

《MFC编程》:第一个MFC程序

《MFC编程》&#xff1a;第一个MFC程序《MFC编程》&#xff1a;第一个MFC程序设置开发环境如何把一个win32程序改成MFC程序&#xff1f;代码书写《MFC编程》&#xff1a;第一个MFC程序 设置开发环境 头文件为<afxwin.h>&#xff1b;在设置中勾选“使用MFC库”。 注&…

53.Isaac教程--ZED相机

ZED相机 ISAAC教程合集地址文章目录ZED相机Codelets支持的固件下载出厂校准文件通过本地校准提高相机精度为相机校准文件指定自定义位置Isaac SDK 支持 StereoLabs ZED 和 ZED Mini (ZED-M) 以及 ZED2 立体相机。 使用本节中的程序下载出厂校准文件或在相机上执行本地校准。 …

看涨期权与看跌期权

目录 1. 看涨期权多头 2. 看涨期权空头 3. 看跌期权多头 4. 看跌期权空头 买进期货合约者称为多头&#xff0c;卖出股指期货合约者称为空头。 1. 看涨期权多头 买入沪深 300 指数的看涨期权&#xff0c;行权价 2000 点&#xff0c;期限 1 个月期权费 100 点1 点 100 元初…

PMP和ACP哪个更有用?

PMP证书和ACP证书都是项目管理类的证书&#xff0c;但是方向不一样&#xff0c;ACP特别验证了从业者在项目工作中理解及实施敏捷管理原则与实践的能力&#xff0c;PMP则认证了从业者所表现出的领导和引导项目团队的能力。 PMP是传统的项目管理模式&#xff0c;适合各行各业&am…

日常小工具之:不花一分钱,不限制视频大小,用 python 和 ffmpeg 批量视频转格式,并保存到 iphone / ipad

应用背景 2008 年左右买的一个系列视频&#xff0c;全都是 .rmvb 的格式&#xff0c;想移到 iphone 里面&#xff0c;但是显示解码格式不支持上 知乎 看格式转换的工具发现这些工具需要把视频上传上去处理&#xff0c;而且很慢&#xff0c;而且有些还限制视频大小 我觉得有必要…

规则引擎-drools-3.4-drl文件构成-rule部分-结果部分Action

文章目录drl文件构成-rule部分结果部分 RHSsetinsert && insertLogicalmodify && updatedeletedrl文件构成-rule部分 drl文件构成&#xff0c;位于官网的第5章位置&#xff0c;也是drools作为规则引擎应用的最核心部分。 其中rule模块&#xff0c;包括属性&am…

Linux命令使用错误记录

问题描述 今天在使用jenkins自动部署的时候&#xff0c;查看日志也是打印成功的&#xff0c;如下图&#xff1a; 自以为是成功&#xff0c;没有看项目启动日志。当访问接口的时候&#xff0c;返回的还是原有数据&#xff0c;没有更新数据接口。 解决思路 首先&#xff0c;打…

春晚背后的“新技术”,腾讯技术助力央视频春晚“新看法”

伴随着《难忘今宵》音乐的响起&#xff0c;兔年春晚圆满落幕。今年&#xff0c;我们和中央广播电视总台一起打造了“竖屏春晚HDR及菁彩声”技术方案&#xff0c;并在“央视频”客户端上线。让你“听”得更沉浸&#xff0c;“看”得更清晰。三维菁彩声&#xff0c;观看春晚“如临…