leetcode-300 最长上升子序列
leetcode-300 最长上升子序列
题目
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
分析
初步分析
第一眼感觉可能使用动态规划。参照leetcode-53最大子序和的分析思路,我们如下分析:
首先以原题目作为状态
a[n]为输入数组的内容
f(n)为前N个元素中最长子序列长度,f(n+1)与f(n)的关系并不明朗
依据leetcode-53的经验转换思路,寻找别的状态,借鉴以第N个元素结尾的子序
g(n)为以第N个元素结尾的最长上升子序列长度,上述的f(n)=max(g(k)) 0<=k<=n
那么以a[n+1]结尾的最长子序列长度可如下计算:
对于那些小于a[n+1]的元素(添加一个a[n+1]后最长上升子序列长度又会+1)把他们对应的g(n)值+1,然后取其中的最大值作为g(n+1)的值
即状态转移方程为:
g(n+1)=max(1,g(k)+1) 条件为(0<=k<=n & a[n+1]>a[k])
因此在求解时,首先是一个for循环遍历N个元素,循环内部需要根据上述状态转移方程遍历之前所有的g(k)值来求解出g(n),并使用一个变量来保存g(n)的最大值
我们可以看到上述动态规划的求解过程时间复杂度为O(N2),其实还有一些可优化的地方,比如 a[i] < a[j]并且g(i)>=g(j)时,那么在计算a[n]时,g(n)的最大值一定不会从a[j]上找到,a[j]此时就是无用的,因此直觉上告诉我们这个方案应该不是最优解
寻找最优解
我们的优化目标就是减少一些不比较的比较。
首先来仔细分析上述状态转移方程,其目标是:找到所有小于a[n+1]的元素,得出其中的最大的g(k),则g(n+1)=上述最大g(k)+1。
思路1:假如我们对前n个元素排序,然后通过二分查找找到所有小于a[n+1]的元素,然后遍历他们的g(n),这个思路引入了插入排序之后还是遍历,并不能达到期望的剪裁效果,所以此路不通
思路2:我们其实并不要一个个元素排序好,我们只是想找g(k)最大的那个小于a[n+1]的元素,所以我们可以对g(k)排序,我们从大到小遍历g(k),如果对应的a[k]小于a[n+1]则可得出g(n+1)=g(k)+1,示例如下所示:
a[n] | g(n) |
---|---|
5 | 3 |
7 | 3 |
2 | 2 |
1 | 1 |
当a[n+1]=6时,由于5<6则g(n+1)=3+1(5这个元素对应的g(n)为3),后续元素就不用再比较了,变化如下:
a[n] | g(n) |
---|---|
6 | 4 |
5 | 3 |
7 | 3 |
2 | 2 |
1 | 1 |
我们再来分析下上述示例,5对应的g(n)是3,7对应的g(n)也是3,那么新来一个元素时,只需要跟5比即可,无需跟7比,即同一个g(n)值只需要保留1个最小值即可,即变化如下:
a[n] | g(n) |
---|---|
6 | 4 |
5 | 3 |
2 | 2 |
1 | 1 |
这样的话,g(n)的值便是从1开始连续递增的,并且最大值或者说保留下来的g(n)的个数就是最终我们要的结果,下次再来一个新元素的话,我们只比较m个元素即可(m=max(g(n))),大大减少了我们的比较量,可以看到这种优化其实就是利用到了上述动态规划中没有利用到的优化点
同时我们再思考下此时相邻g(n)之间他们对应的a[n]之间的关系。比如元素b对应的g(n)=2,元素c对应的g(n)=3,有没有可能c<b?假如c小于b,那么在得出c对应的g(n)=3的过程中,必然是g(n)=2的元素+1得到,则必然g(n)=2对应的元素小于c才会去执行+1操作来得到c对应的g(n)值,所以必然有c<b,即g(n)是从大到小排好序的,对应的a[n]也是从大到小排好序的,他们的个数即为我们要的最终结果
所以最终在实施的时候,对a[n]进行排序,假如新来一个元素比最大a[n]要大,则该元素直接填充,如果新来一个元素介于2个元素b、c之间,c>b,必有c对应的g(n)值=b对应的g(n)值+1,那么根据上述求g(n)的逻辑,该元素的对应的g(n)值=b对应的g(n)值+1即和c对应的g(n)值是一样的,再利用同一个g(n)值取最小的特点,那么新来的元素就要替换掉c
以上述为例,再来一个元素3时,3介于5和2之间,3对应的g(n)值=2对应的g(n)值+1,则3和5的g(n)值同为3,再利用相同g(n)值取最小的逻辑,最终演变成3替换掉第一个大于它的值即5
a[n] | g(n) |
---|---|
6 | 4 |
5 | 3 |
3 | 3 |
2 | 2 |
1 | 1 |
演变成
a[n] | g(n) |
---|---|
6 | 4 |
3 | 3 |
2 | 2 |
1 | 1 |
综上所述:新来一个元素如果大于上述a[n]中的最大值则填充,否则就要替换第一个大于该值的位置(即通过二分查找即可)
代码
动态规划的解法如下,时间复杂度O(n2):
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int fn = 1;
int[] gnArr = new int[nums.length];
gnArr[0] = 1;
for (int i = 1; i < nums.length; i++) {
int max = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
max = Math.max(max, gnArr[j] + 1);
}
}
gnArr[i] = max;
fn = Math.max(fn, max);
}
return fn;
}
第二套优化方案,时间复杂度O(nlogn)
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] d = new int[nums.length];
int pos = 0;
d[pos] = nums[0];
for (int i = 1; i < nums.length; i++) {
if (nums[i] > d[pos]) {
pos++;
d[pos] = nums[i];
} else {
d[binarySearch(d, pos, nums[i])] = nums[i];
}
}
return pos + 1;
}
private int binarySearch(int[] arr, int end, int key) {
int left = 0;
int right = end;
int mid;
while (left < right) {
mid = left + (right - left) / 2;
if (arr[mid] >= key) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
跑分
欢迎关注微信公众号:乒乓狂魔

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
再谈使用开源软件搭建数据分析平台
三年前,我写了这篇博客使用开源软件快速搭建数据分析平台, 当时收到了许多的反馈,有50个点赞和300+的收藏。到现在我还能收到一些关于dataplay2的问题。在过去的三年,开源社区和新技术的发展可谓日新月异,我希望试试利用最新的技术来帮助没有数据科学背景的人也能够轻松的进行数据分析和预测,于是就有了dataplay3 。 架构 老规矩,先上架构图: 为了构建一个最简单的开箱即用的数据分析平台,我使用了如下的技术栈: 服务器端: sanic基于Python3的web服务器 pandasPython上最流行的数据分析库 auto-sklearn基于sklearn的自动机器学习库 prophet非死不可开源的时间序列分析库 pandassql能够在Panda数据框上运行SQL的库 gunicorn基于python的WSGI HTTP服务器 客户端: React前端框架 Ant Design Pro蚂蚁开源的企业级应用框架套装 Ant Design UmiJS Dva BizCharts基于G2和React的可视化库 G2The Grammar of Graphics in JavaScri...
-
下一篇
并发编程专题五-AbstractQueuedSynchronizer源码分析
PS:外号鸽子王不是白来的,鸽了好几天,也是因为比较忙,时间太少了,这篇东西有点多,需要慢慢消化。不知不觉居然写了4个多小时.... 一、什么是AQS aqs是AbstractQueuedSynchronizer的简称,是用来构建锁或者其他同步组件(信号量、事件等)的基础框架类。JDK中许多并发工具类的内部实现都依赖于AQS,如ReentrantLock, Semaphore, CountDownLatch等等。 二、AQS的设计模式 2.1模板方法设计模式 在学习原理和源码之前,我们先了解一中设计模式。模板方法设计模式。 模板方法设计模式定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。 通俗来说就是完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。 例如 发短信有以下有以下四个步骤: 1,需要发送给的人; 2编写内容; 3,发送日期 4,调用短信网关发送 发邮件也同...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2全家桶,快速入门学习开发网站教程
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- MySQL数据库在高并发下的优化方案
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker快速安装Oracle11G,搭建oracle11g学习环境