找出最具竞争力的子序列_力扣300——最长上升子序列

这道题主要涉及动态规划,优化时可以考虑贪心算法和二分查找。

原题

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

解题

暴力法

这也是最基础的想法,利用递归,从每一个数开始,一个一个寻找,只要比选中的标准大,那么就以新的数为起点,继续找。全部找完后,找出最长的序列即可。

也看一下代码:

class Solution {    public int lengthOfLIS(int[] nums) {        // 递归查询        return recursiveSearch(nums, Integer.MIN_VALUE, 0);    }    public int recursiveSearch(int[] nums, int standard, int index) {        if (nums.length == index) {            return 0;        }                // 如果包含当前index的数字,其递增长度        int tokenLength = 0;        if (nums[index] > standard) {            tokenLength = 1 + recursiveSearch(nums, nums[index], index + 1);        }        // 如果不包含当前index的数字,其递增长度        int notTokenLength = recursiveSearch(nums, standard, index + 1);        // 返回较大的那个值        return tokenLength > notTokenLength ? tokenLength : notTokenLength;    }}

提交之后报超出时间限制,这个也是预料到的,那么我们优化一下。

记录中间结果

仔细分析一下上面的暴力解法,假设 nums 是: [10,9,2,5,3,7,101,18],那么从 7 到 101 这个查找,在2、5、3的时候,都曾经查找过一遍。

那么针对这种重复查找的情况,我们可以用一个二维数组,记录一下中间结果,这样就可以达到优化的效果。比如用int[][] result标记为记录中间结果的数组,那么result[i][j]就代表着从 nums[i - 1] 开始,无论包含还是不包含 nums[j] 的最大递增序列长度。这样就能保证不再出现重复计算的情况了。

让我们看看代码:

class Solution {    public int lengthOfLIS(int[] nums) {        // 记录已经计算过的结果        int result[][] = new int[nums.length + 1][nums.length];        for (int i = 0; i < nums.length + 1; i++) {            for (int j = 0; j < nums.length; j++) {                result[i][j] = -1;            }        }        // 递归查询        return recursiveSearch(nums, -1, 0, result);    }    public int recursiveSearch(int[] nums, int preIndex, int index, int[][] result) {        if (nums.length == index) {            return 0;        }        // 如果已经赋值,说明计算过,因此直接返回        if (result[preIndex + 1][index] > -1) {            return result[preIndex + 1][index];        }        // 如果包含当前index的数字,其递增序列最大长度        int tokenLength = 0;        if (preIndex < 0 || nums[index] > nums[preIndex]) {            tokenLength = 1 + recursiveSearch(nums, index, index + 1, result);        }        // 如果不包含当前index的数字,其递增序列最大长度        int notTokenLength = recursiveSearch(nums, preIndex, index + 1, result);        // 返回较大的那个值        result[preIndex + 1][index] = tokenLength > notTokenLength ? tokenLength : notTokenLength;        return result[preIndex + 1][index];    }}

提交OK,但是结果感人,几乎是最慢的了,无论时间还是空间上,都只打败了5%左右的用户,那就继续优化。

    ### 动态规划

假设我知道了从 nums[0] 到 nums[i] 的最大递增序列长度,那么针对 nums[i + 1],我只要去跟前面的所有数比较一下,找出前面所有数中比 nums[i + 1] 小的数字中最大的递增子序列,再加1就是 nums[i + 1] 对应的最大递增子序列。

这样我只要再记录一个最大值,就可以求出整个数组的最大递增序列了。

让我们看看代码:

class Solution {    public int lengthOfLIS(int[] nums) {        if (nums.length == 0) {            return 0;        }        // 动态规划,之前几个数字中,有几个比当前数小的,不断更新        // 存储中间结果        int[] dp = new int[nums.length];        // 最大值,因为数组中至少有一个,所以最小是1        int max = 1;                // 遍历        for (int i = 0; i < dp.length; i++) {            // 当前下标i的最大递增序列长度            int currentMax = 0;            for (int j = 0; j < i; j++) {                // 如果nums[i]比nums[j]大,那么nums[i]可以加在nums[j]后面,继续构成一个递增序列                if (nums[i] > nums[j]) {                    currentMax = Math.max(currentMax, dp[j]);                }            }            // 加上当前的数            dp[i] = currentMax + 1;            max = Math.max(dp[i], max);        }        return max;    }}

提交OK,执行用时:9 ms,只战胜了75.15%的 java 提交,看来还是可以继续优化的。

贪心算法 + 二分查找

贪心算法意味着不需要是最完美的结果,只要针对当前是有效的,就可以了。

我们之前在构造递增序列的时候,其实是在不断根据之前的值进行更新的,并且十分准确。但其实并不需要如此,只要保证序列中每个数都相对较小,就可以得出最终的最大长度。

还是以 [10,9,2,5,3,7,101,18,4,8,6,12]举例:

  1. 从10到2,都是无法构成的,因为每一个都比之前的小。
  2. 当以最小的2作为起点后,2,5、2,3都是可以作为递增序列,但明显感觉2,3更合适,因为3更小。
  3. 因为7大于3,因此递增序列增长为2,3,7。
  4. 因为101也大于7,因此递增序列增长为2,3,7,101。
  5. 因为18小于101,但是大于7,因此我们可以用18替换101,因为18更小,序列更新为2,3,7,18
  6. 此时遇到4,4大于3但是小于7,我们可以用它替换7,虽然此时新的序列2,3,4,18并不是真正的结果,但首先长度上没有问题,其次如果出现新的可以排在最后的数,一定是大于4的,因为要先大于现在的最大值18。序列更新为2,3,4,18。
  7. 同理,8大于4小于18,替换18,此时新的序列2,3,4,8,这样是不是大家开始懂得了这个规律。
  8. 遇到6之后,更新为2,3,4,6。
  9. 遇到12后,更新为2,3,4,6,12。

这样也就求出了最终的结果。

结合一下题目说明里提到的O(nlogn),那么就可以想到二分查找,运用到这里也就是找到当前数合适的位置。

接下来让我们看看代码:

class Solution {    public int lengthOfLIS(int[] nums) {        if (nums.length == 0) {            return 0;        }        // 贪心 + 二分查找        // 一个空数组,用来存储最长递增序列        int[] result = new int[nums.length];        result[0] = nums[0];        // 空数组的长度        int resultLength = 1;        // 遍历        for (int i = 1; i < nums.length; i++) {            int num = nums[i];            // 如果num比当前最大数大,则直接加在末尾            if (num > result[resultLength - 1]) {                result[resultLength] = num;                resultLength++;                continue;            }            // 如果和最大数相等,直接跳过            if (num == result[resultLength - 1]) {                continue;            }            // num比最大值小,则找出其应该存在的位置            int shouldIndex = Arrays.binarySearch(result, 0, resultLength, num);            if (shouldIndex < 0) {                shouldIndex = -(shouldIndex + 1);            }            // 更新,此时虽然得出的result不一定是真正最后的结果,但首先其resultLength不会变,之后就算resultLength变大,也是相对正确的结果            // 这里的更新,只是为了让result数组中每个位置上的数,是一个相对小的数字            result[shouldIndex] = num;        }        return resultLength;    }}

提交OK,执行用时:2 ms,差不多了。

总结

以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题目用动态规划其实就已经能解决了,但为了优化,还需要用到贪心算法和二分查找。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

公众号:健程之道

0ae87e6faa4e032b9a59f2863d6f87e9.png

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

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

相关文章

java 跨站点脚本编制_AppScan跨站点脚本编制修复

查了下百度&#xff0c;跨站点脚本编制其实也就是在url后加入参数和js脚本实现一些坏坏的事情&#xff0c;至少appscan就是这么干的。那么主要的工作就是把恶意代码给过滤了&#xff0c;作为javaweb开发&#xff0c;明显第一步想到的是过滤器。网上很多都是将request对象 传入H…

python怎么安装本地的egg_python egg怎么安装

经常接触Python的同学可能会注意到&#xff0c;当需要安装第三方python包时&#xff0c;可能会用到easy_install命令。easy_install是由PEAK(Python Enterprise Application Kit)开发的setuptools包里带的一个命令&#xff0c;它用来安装egg包。egg包是目前最流行的python应用打…

java 动态字符串_Java动态编译执行一串字符串,类似于Javascript里的eval函数

Javascript里的eval函数能动态执行一串js脚本。那Java里怎么做到呢。有两种方法:一种是使用可以执行js脚本的Java类 ScriptEngineManagerpublic static void main(String args[]){ScriptEngineManager manager new ScriptEngineManager();ScriptEngine se manager.getEngineB…

jquery全局变量_jQuery源码一个小细节,却很好地体现了性能优化的思想,很优秀...

写在前面听闻大佬们在写一些框架或者库的时候&#xff0c;到处都隐藏了一些细节&#xff0c;所以阅读他们的源代码&#xff0c;无论从性能优化、还是JS API的学习、亦或是代码风格等方面给到我们很多启发。这两天我翻看了一下jQuery1.x的源代码&#xff0c;看到了这么一段&…

java connection 共享_java 使用HttpURLConnection发送数据简单实例

java 使用HttpURLConnection发送数据简单实例每个 HttpURLConnection 实例都可用于生成单个请求&#xff0c;但是其他实例可以透明地共享连接到 HTTP 服务器的基础网络。请求后在 HttpURLConnection 的 InputStream 或 OutputStream 上调用 close() 方法可以释放与此实例关联的…

mockito mock void方法_Spock如何模拟抽象类方法

我们平时写单元测试时经常会遇到调用抽象类或父类的方法&#xff0c;这些抽象方法可能是调用底层接口或数据库&#xff0c;需要mock掉&#xff0c;让抽象方法返回一个我们指定的值&#xff0c;以便测试当前代码逻辑的场景。下面讲下Spock如何结合power mock实现动态mock抽象方法…

new 实例化对象是啥意思_二. 初步认识JS中的类和对象

1 构造函数的定义在JS中, 没有类(class)的概念, 主要是通过构造函数来模拟的.语法function 构造函数名 () {// 函数体}使用function关键字表示定义一个构造函数构造函数名一般首字母大写示例function Person() {}通过以上方式就可以定义一个Person构造函数, 相当于定义好了一个…

java get 空指针_Java 中空指针处理方法

空指针异常(Null Pointer Exception)是我们平时最容易碰到的&#xff0c;也是最令人讨厌的异常。本文介绍如何避免出现空指针异常。首先我们看如下的示例&#xff1a;private Boolean isFinished(String status) {if (status.equalsIgnoreCase("Finish")) {return Bo…

正则匹配问号_爬虫之正则表达式

1什么是正则表达式正则表达式&#xff0c;也称规则表达式&#xff08;Regular Expression,在代码中常简写为RE&#xff09;。2为什么使用用来匹配、替换一类具有相同规则字符串3使用规则3.1单字符&#xff1a;3.2数量修饰&#xff1a;3.3边界&#xff1a;3.4分组&#xff1a;3.…

java farm tycoon_Idle Farm Tycoon

详情Have you always wanted to run your own farm? Now you can fulfill your dream!To start things off, begin with a few wheat farms. Once the first money is rolling, you can purchase new crops, bushes, trees and animals!Dont be afraid of running out of spac…

java treeset subset_Java中TreeSet的详细用法

第1部分 TreeSet介绍TreeSet简介TreeSet 是一个有序的集合&#xff0c;它的作用是提供有序的Set集合。它继承于AbstractSet抽象类&#xff0c;实现了NavigableSet, Cloneable, java.io.Serializable接口。TreeSet 继承于AbstractSet&#xff0c;所以它是一个Set集合&#xff0c…

sql执行有时候快有时候慢_如何让你的 SQL 执行的飞起?

OR 不能瞎用午饭间的小 C&#xff0c;答应着一起吃饭&#xff0c;却眼不离屏。我知道准是上午人甲产品经理又来了一个脏活。话说 SQL 程序员本身是个光荣的职业&#xff0c;顷刻间百万数据、百亿金额从指间流过&#xff0c;心都不带咯噔的。在心如止水的 SQL 编码师眼里&#x…

binaryformatter java_Java,C#使用二进制序列化、反序列化操作数据

java使用二进制序列化、反序列化的操作首先&#xff0c;要引入java.io下面相关包&#xff0c;或者直接写import java.io.*;下面&#xff0c;为了书写操作的方便&#xff0c;采用复制文件&#xff0c;和throws声明异常的方式来写public void test6() throws IOException {byte[]…

未备案域名临时跳过备案提示_做好了网页,有域名和服务器,还要怎么搭建网站?...

不知道你选择的服务器是国内大陆的还是国外或者香港的&#xff0c;如果是国内大陆的服务器我们的网站域名还需要备案&#xff0c;你的服务器提供商是那家就在那家备案&#xff0c;备案流程跟着提示走就可以了&#xff0c;接下来我们开始正式进入将网站三要素&#xff08;域名、…

python怎么退出help_(转)python中如何使用help命令?

查看python所有的modules&#xff1a;help("modules")单看python所有的modules中包含指定字符串的modules&#xff1a; help("modules yourstr")查看python中常见的topics&#xff1a; help("topics")查看python标准库中的module&#xff1a;imp…

louvain算法_单细胞聚类(四)图解Leiden算法对Louvain算法的优化

Louvain算法是目前单细胞分析中最常用的聚类算法[1]&#xff0c;Seurat/Scanpy/RaceID等单细胞分析工具都默认louvain算法。6天前HumanCell Atlas(HCA)团队发表在Nature Method上的单细胞分析流程中[2]&#xff0c;默认的聚类算法是scran包的方法&#xff1a;细胞间权重基于排序…

java 动态绑定原理_详解Java动态绑定机制的内幕(图)

在Java方法调用的过程中&#xff0c;JVM是如何知道调用的是哪个类的方法源代码&#xff1f; 这里面到底有什么内幕呢&#xff1f; 这篇文章我们就将揭露JVM方法调用的静态(static binding) 和动态绑定机制(auto binding) 。静态绑定机制//被调用的类package hr.test;class Fath…

python重新加载模块_jupyter实现重新加载模块

最近几年&#xff0c;jupyter在全球数据科学领域&#xff0c;已经成为不可或缺的重要工具。在jupyter中用python写程序&#xff0c;若import了自己写的外部模块&#xff0c;如果这个外部模块有更新&#xff0c;再次执行import&#xff0c;jupyter是不会重新导入的。一般的做法是…

java连接access2013数据库_滴水穿石–Java连接Access数据库及其操作

1、配置数据源【控制面板】—>【管理工具】—>【数据源ODBC】点击添加选择Microsoft Access Driver填写数据源名(自定义&#xff0c;如test)&#xff0c;并选择数据库(指定你的Access数据库文件)&#xff0c;如下图红色箭头标注最后&#xff0c;点击确定数据源配置完成2、…

python抠透明图_python利用蒙版抠图(使用PIL.Image和cv2)输出透明背景图

因为最近在做深度学习抠图&#xff0c;正好要用到蒙版进行抠图&#xff0c;所以我将抠图代码进行了封装注释&#xff0c;可以直接使用。可能走了弯路&#xff0c;若有高见请一定提出&#xff01;主要代码import cv2from PIL import Imageimport numpy as npclass UnsupportedFo…