程序员面试100题之十二:求数组中最长递增子序列

《编程之美》——求数组的最长递增子序列 问题: 求数组最长递增子序列。分析与解法: 【解法一】 对数组进行排序,将有序的数组与原数组一起求最长公共子序列,于是原问题可以转化为求最长公共子序列的问题。排序的时间复杂度为O(nlogn),求最长公共子序列的问题时间复杂度为O(n^2),总的时间复杂度为O(n^2)。【解法二】 使用动态规划的方法,因为要求长度为i的序列的Ai{a1,a2,……,ai}最长递增子序列,需要先求出序列Ai- 阅读详情
      写一个时间复杂度尽可能低的程序,求一个一维数组(N个元素)中最长递增子序列的长度。

     例如:在序列1,-1,2,-3,4,-5,6,-7中,其最长递增子序列为1,2,4,6。

     分析与解法

     根据题目要求,求一维数组中的最长递增子序列,也就是找一个标号的序列b[0],b[1],... b[m](0<=b[0]<b[1]<...<b[m]<N),使得array[b[0]]< array[b[1]]<...<array[b[m]]。

     解法一

      根据无后效性的定义我们知道,将各阶段按照一定的次序排列好之后,对于某个给定阶段的状态来说,它以前各阶段的状态无法直接影响它未来的决策,而只能间接地通过当前状态来影响。换句话说,每个状态都是过去历史的一个完整总结。

      同样地,仍以序列1,-1,2,-3,4,-5,6,-7中为例,我们在找到4之后,并不关心4之前的两个值具体是怎样,因为它对找到6并没有直接影响。因此,这个问题满足无后效性,可以使用动态规划来解决。

     可以通过数字的规律来分析目标串:1,-1,2,-3,4,-5,6,-7。

     使用i来表示当前遍历的位置:

     当i=1时,显然,最长的递增序列为(1),序列长度为1.

     当i=2时,由于-1<1。因此,必须丢弃第一个值然后重新建立串。当前的递增序列为(-1),长度为1.

     当i=3时,由于2>1,2>-1。因此,最长的递增序列为(1,2),(-1,2),长度为2。在这里,2前面是1还是-1对求出后面的递增序列没有直接影响。

     依次类推之后,可以得出如下的结论。

     假设在目标数组array[]的前i个元素中,最长递增子序列的长度为LIS[i]。那么,

     LIS[i+1]=max{1,LIS[k]+1},array[i+1]>array[k],for any k<=i

     即如果array[i+1]大于array[k],那么第i+1个元素可以接在LIS[k]长的子序列后面构成一个更长的子序列。与此同时array[i+1]本身至少可以构成一个长度为1的子序列。

    根据上面的分析,可以得到如下的代码:

int LIS(int[] array)
{
	int *LIS = new int[array.Length];
	for(int i = 0; i < array.Length; i++)
	{
		LIS[i] = 1;          //初始化默认的长度
		for(int j = 0; j < i; j++)        //前面最长的序列
		{
			if(array[i] > array[j] && LIS[j] + 1 > LIS[i])
			{
				LIS[i] = LIS[j] + 1;
			}
		}
	}
	return Max(LIS);      //取LIS的最大值
}

     这种方法的时间复杂度为O(N^2+N)= O(N^2)。

     解法二

 显然O(N^2)的算法只是一个比较基本的解法,我们须要想想看是否能够进一步提高效率。在前面的分析中,当考虑第i+1个元素的时候,我们是不考虑前面i个元素的分布情况的。现在我们从另一个角度分析,即当考虑第i+1个元素的时候考虑前面i个元素的情况。

       对于前面i个元素的任何一个递增子序列,如果这个子序列的最大的元素比array[i+1]小,那么就可以将array[i+1]加在这个子序列后面,构成一个新的递增子序列。

      比如当i=4的时候,目标序列为:1,-1,2,-3,4,-5,6,-7最长递增序列为:(1,2),(-1,2)。那么,只要4>2,就可以把4直接增加到前面的子序列形成一个新的递增子序列。

     因此,我们希望找到前i个元素中的一个递增子序列,使得这个递增子序列的最大的元素比array[i+1]小,且长度尽量地长。这样将array[i+1]加在该递增子序列后,便可找到以array[i+1]为最大元素的最长递增子序列。

    仍然假设在数组的前i个元素中,以array[i]为最大元素的最长递增子序列的长度为LIS[i]。

    同时,假设:

    长度为1的递增子序列最大元素的最小值为MaxV[1];

    长度为2的递增子序列最大元素的最小值为MaxV[2];

     ......

     长度为LIS[i]的递增子序列最大元素的最小值为MaxV[LIS[i]]。

     假如维护了这些值,那么,在算法中就可以利用相关的信息来减少判断的次数。

     具体算法实现如代码所示:

int LIS(int array[])
{
	//记录数组中的递增序列信息
	int *MaxV = new int[array.Length + 1];

	MaxV[1] = array[0];             //数组中的第一值,边界值
	MaxV[0] = Min(array) - 1;       //数组中最小值,边界值
	int *LIS = new int[array.Length];

	//初始化最长递增序列的信息
	for(int i = 0;i < LIS.Length; i++)
	{
		LIS[i] = 1;
	}

	int nMaxLIS = 1;       //数组最长递增子序列的长度
	for(int i = 1; i < array.Length; i++)
	{
		//遍历历史最长递增序列信息
		int j;
		for(j = nMaxLIS; j >=0; j--)
		{
			if(array[i] > MaxV[j])
			{
				LIS[i] = j + 1;
				break;
			}
		}

		//如果当前最长序列大于最长递增序列长度,更新最长信息
		if(LIS[i] > nMaxLIS)
		{
			nMaxLIS = LIS[i];
			MaxV[LIS[i]] = array[i];
		}
		else if(MaxV[j] < array[i] && array[i] < MaxV[j + 1])
		{
			MaxV[j + 1] = array[i];
		}
	}
	return nMaxLIS;
}

由于上述解法中的穷举遍历,时间复杂度仍然为O(N^2)。

       解法三
解法二的结果似乎仍然不能让人满意。我们是否把递增序列中间的关系全部挖掘出来了呢?再分析一下临时存储下来的最长递增序列信息。

在递增序列中,如果i<j,那么就会有MaxV[i]<MaxV[j]。如果出现MaxV[j]<MaxV[i]的情况,则跟定义矛盾,为什么?

因此,根据这样单调递增的关系,可以将上面方法中的穷举部分进行如下修改:

for(int j = LIS[i - 1]; j >= 1; j--)
{
	if(array[i] > MaxV[j])
	{
		LIS[i] = j + 1;
		break;
	}
}

如果把上述的查询部分利用二分搜索进行加速,那么就可以把时间复杂度降为O(N*log2N)。

       小结

       从上面的分析中可以看出我们先提出一个最直接(或者说最简单)的解法,然后从这个最简单解法来看是否有提升的空间,进而一步一步地挖掘解法中的潜力,从而减少解法的时间复杂度。

      在实际的面试中,这样的方法同样有效。因为面试者更加看中的是应聘者是否有解决问题的思路,不会因为最后没有达到最优算法而简单地给予否定。应聘者也可以先提出简单的办法,以此投石问路,看看面试者是否会有进一步的提示。

数据结构与算法之最长递增子序列 阅读详情

相关推荐

AI办公Agent技术:文档与表格智能处理实践

办公自动化技术正从规则驱动转向语义理解,大语言模型(LLM)为办公Agent赋予了智能决策能力。这类系统通过五层架构整合自然语言处理、工具API调用和任务规划引擎,能自动完成文档分类、表格分析等高重复性工作。关键技术包括基于TF-IDF和BERT的特征提取、Excel公式自动生成等,在财务报告、法律审查等场景可实现90%以上的效率提升。随着多模态能力和领域专业化发展,办公Agent正在重塑企业文档处理流程,成为数字化转型的核心基础设施。

weixin_30263277的博客 519

求数组中最长递增子序列

例如在序列1,-1,2,-3,4,-5,6,-7中,其最长的递增子序列为1,2,4,6。 解法一:假设在目标数组array[]的前i个元素中,最长递增子序列的长度为LIS[i]。那么,LIS[i+1]=max{1,LIS[k]+1},array[i+1]>array[k],forany kLIS[i]是指:假设在数组的前i个元素中,以array[i]为最大元素的最长递增子序列的长度)。 思想:

xflame的专栏 846

Matterport3D数据集介绍与下载(全网最详细)

Matterport3D数据集介绍与下载(全网最详细)

shannanshouyin的博客 2168

求一个数组的最长递增子序列(动态规划经典问题)

1,给定一个整数数组,求它的一个最长递增子序列,求出其长度和对应的子序列。要求时间复杂度为O(n^2) (对应算法导论 习题15.4-5)解答:这个算法和求最长递减子序列比较类似。我们定义原始数组为a[1],a[2],...,a[n],定义S[i]为以a[i]结尾的最长递增子序列的长度,那么状态转换函数就为:s[i]=max{s[r]| 1=<r<i,a[i]>a[r]}+1。初...

weixin_30471561的博客 1064

Linux之GTK系列教程

00、背上行囊1——程序员学习之道 01、背上行囊2——为什么要学习GTK 02、背上行囊3——GUI概述 03、背上行囊4——GTK简介 04、背上行囊5——GTK环境搭建( Linux ) 05、背上行囊6——GTK环境搭建( windows ) 06、踏上旅途1——一个简单的空白窗口 07、踏上旅途2——控件的添加 08、踏上旅途3——布局容器之水平布局 09、踏上旅途

每天积累一点点 2万+

UE5 中 Python 编程:Python 代码加载到 UE5 中,如何供蓝图节点调用

配置 python 代码的路径,使 UE5 启动时,加载代码文件。重启之后,在蓝图中右键,就能搜索到你定义的方法了。参数为 str 字符串类型,返回值为 向量数组。注意:一定要 重启 UE5。

topcss的专栏 3996

最长递增子序列

问题 给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4. 解法1:最长公共子序列法 这个问题可以转换为最长公共子序列问题。如例子中的数组A{5,6, 7, 1, 2, 8},则我们排序该数组得到数组A‘{1, 2, 5, 6, 7,...

切梦 5419

量化策略分享之ST股弱转强,回测年化123%

本策略的核心是精选ST股票,国九条过滤,防止直接暴雷退市,只做ST股票的弱转强,弱转强的认定标准是前日涨停但昨日不涨停,昨日收盘价大于前一日最低价,昨日收盘价大于10日线,昨日成交量大于前一日的成交量但小于前一日成交量的10倍,股价大于1;这类股票强势,有资金介入,能获得超额收益。

qq_29301281的博客 1268

程序员面试100题之十二 求数组中最长递增子序列

程序员面试100题之十二 求数组中最长递增子序列

fguihbfg的博客 268

单路径微分相除和微分自乘相减算法(PGC-SDD-DSM)

PGC-SDD-DSM算法通过融合单路微分相除和差分自乘,解决了传统PGC解调技术对调制深度和光强干扰敏感的问题,显著提升了解调稳定性和抗失真能力。其高信噪比、低谐波失真以及对参数变化的鲁棒性,使其在光纤传感领域具有重要应用价值。PGC-SDD-DSM算法在计算复杂度方面具有显著的优势,主要体现在其高稳定性和低谐波失真上。这些优势使其在光纤光学干涉传感器中表现出色,尤其是在面对光强干扰和调制深度变化时。

百态·数智思维 | 聚焦大数据、量化与未来AI 841

第11.4节 混合储能系统能量管理

混合储能系统(HESS)的能量管理策略采用分层架构实现多时间尺度优化:调度层(小时级)进行经济优化,协调层(秒级)基于频率分解分配功率,执行层(毫秒级)实现快速跟踪。深度强化学习(DRL)方法通过定义状态空间、动作空间和多目标奖励函数,能有效处理系统不确定性和多目标优化问题。HESS性能评估需综合技术指标(如调节性能Kp值、设备寿命指标)和经济指标(净现值NPV、平准化储能成本LCOS),并遵循"建模-仿真-实验"的闭环流程。

技术工作者 246

2022年第十三届蓝桥杯 python B组 第I题 最长不下降子序列

由于在csdn上发现的各种大佬写的题解在测试网站中都是错的或者最多跑n<100的案例,所以我只能自己研究该题,在苦苦思索多日后我终于有了一套自己的方法,在测试网站中可以通过n大于上千的案例,但仍然过不了上万的案例,如果有大佬知道更好的办法,请在评论区留言。 其实这道题有一个更简单的版本,在左程云写的《程序员代码面试指南》——最长递增子序列 我在简单描述这本书中的这个题之前,我们应该先明确什么是子序列,子序列就是一个序列中抽出来的(未必连续),如 1 4 2 8 5 中子序列有:1 4 8,1 4

qq_55364042的博客 4075

Xilinx DDR3 MIG IP核设计--汇总导航

快速上手Xilinx DDR3 IP核----汇总篇

孤独的单刀 2万+

最长递增子序列,(搜狐[编程题]马戏团)

程序员代码面试指南(左程云)读书笔记  第四章 最长递增子序列,(搜狐[编程题]马戏团) 题目:给定数组arr ,返回arr的最长递增子序列。 例如:arr=[2,1,5,3,6,4,8,9,7] 返回【1,3,4,8,9】 先介绍O(N*N)的方法, 1,生成长度为N数组dp,dp[i]表示在以arr[i]这个数结尾的情况下,arr[0..i]中的最大递增子序列长度。 2,对第一个

OY_palada 841

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

[学习交流] 100家IT名企面试总结------Java篇

在广州黑马程序员学习完后,我收集了以下的面试总结,以方便一个星期后的面试。 一. 算法 • (一)经典的算法: • 1.递归算法: • 1)数据定义按递归定 义 fibonacci • 2)问题揭发按递归解 决 回溯算法 • 3)数据结构按递归定 义 树遍历图搜索 • 经典题-整数划分: (一)经典的算法: • ...

xiaoyaGrace的博客 272

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

告别死记硬背:40岁程序员如何深度理解 LIS 算法?(从 $O(n^2)$ 到 $O(n \log n)$)

摘要: 本文以最长递增子序列(LIS)问题为例,探讨中年程序员如何通过“插槽重构”思维掌握算法。核心策略是固定结尾并记录最佳战绩($O(n^2)$),升级为用tails数组管理插槽以优化兼容性($O(n\log n)$)。关键点在于替换旧数字以降低准入门槛,为未来创造可能性,并通过二分查找快速定位替换位置。代码实现简洁,强调屏蔽公式、关注长度而非内容,以及根据递增类型微调二分策略。最终指出算法学习应聚焦决策思想而非细节实现,逻辑直觉才是核心。

goodparty的专栏 683

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

2024年最新大一就开始这样刷题后,我成了同学们眼中的 offer 收割机,一个Java程序员的面试心得

看完上述知识点如果你深感Java基础不够扎实,或者刷题刷的不够、知识不全面小编专门为你量身定制了一套针对知识面不够,也莫慌!还有一整套的,可以瞬间查漏补缺全都是一丢一丢的收集整理纯手打出来的。

2401_84584796的博客 377

LeetCode 回溯04 | 491. 递增子序列、46. 全排列、47. 全排列 II

LeetCode 491. 递增子序列、46. 全排列、47. 全排列 II

m0_37830008的博客 228

Leetcode Hot100:算法面试通关秘籍与高效刷题指南

数据结构与算法是程序员面试的核心考察点,其中动态规划和哈希表优化等关键技术常出现在大厂面试中。通过系统性地掌握这些基础算法原理,开发者可以显著提升解题效率。Leetcode Hot100作为经典题库,精选了100道覆盖数组、链表、二叉树等高频考点的高质量题目,能帮助求职者建立完整的算法思维框架。在实际应用中,如处理两数之和等哈希表问题时,掌握空间复杂度优化技巧尤为关键。本文通过三遍刷题法等实战方法论,结合调试技巧和复杂度分析,为开发者提供从入门到精通的系统训练路径。

weixin_34416649的博客 328

面试秘籍,左神532页刷题宝典:助你征服大厂面试

本书的所有代码示例均为Java实现,但这并不会成为其他语言使用者的阅读障碍。在编写代码时,我们特别注意避免与Java语言特性紧密相关的写法,并尽量遵循多数编程语言的通用习惯。因此,将本书中的Java实现转换为其他编程语言是非常便捷的。如果你需要完整版PDF学习资源请扫描下方名片100%获取免费下载方式!👇👇点击下方名片👇👇。

资料免费分享,点击名片 419
上一篇: 程序员面试100题之十一:数组循环移位
下一篇: getchar、scanf以及缓冲区的概念
hackbuteer1
博客等级 码龄15年 1万+粉丝 251原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值