基于二分查找的动态规划 leetcode 300.最长递增子序列

news2024/9/27 22:04:50

如题:

https://leetcode.cn/problems/longest-increasing-subsequence/description/

其实常规动态规划的解法就没什么好说的了,有意思的是官方放出了一个二分查找的动态规化解法,时间复杂度能降到O(nlog(n)),但是为什么这样能解,似乎讲的不是那么清楚。

另外,即使是从操作步骤及状态转移函数上来说,可能俄罗斯套娃的第二个解里面还说的更清楚一点~~。具体可以去看题解:

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/russian-doll-envelopes/solutions/633231/e-luo-si-tao-wa-xin-feng-wen-ti-by-leetc-wj68/注:因为俄罗斯套娃信封问题,最终转换成了最长递增子序列问题,所以在求最长递增子序列的时候的解法是一模一样的,下面的截图就是354.俄罗斯套娃信封问题的官方解二。

这里面最关键的就是“f[j]表示h的前i个元素可以组成的长度为j的最长严格递增子序列的末尾元素的最小值”这一点,只要f[j]满足这一条件(状态转移的公式也完全是按照这个概念来的),并且把所有有定义的f[j]都计算完,那么最大的j就是最长严格递增子序列的长度(j从0开始,则长度就是最大的j加1啦)。

一。初步实操

不过这个概念似乎本身就有点绕啊,结合一下实际操作更容易理解,并且操作过程我稍微加点东西(参考的是laboladong的算法笔记里面的patience game纸牌游戏,不过他也没有说这么解为什么一定行)

数组就用题目里的,即nums=[0,3,1,6,2,2,7],f函数也可以当成一个列表,长度会一点点增长,元素值也可能会更新,一开始f是空的。

第1步,直接把nums第0个元素加到f中,即f[0]=0

此时的f[0]的含义就是当前长度为1的严格递增子序列的末尾元素的最小值就是0

第2步,取nums第1个元素,值为3,它明显比f[0]=0大,所以把它放到f[1]

此时的f[1]的含义就是当前长度为2的严格递增子序列的末尾元素的最小值就是3

其实f[j]的含义永远不会变啦,只是值可能会变,后不缀述。

第3步,取nums[2]=1,它比f[0]=0大,比f[1]=3小,所以更新f[1]的值为1。

我为什么把1写在3的下面,没有把3擦掉?后面自有妙用,并且这就是patience game纸牌游戏的玩法。

注意,我们更新的位置就是大于等于1的最小值。即f=[0, 3],找1的位置,自然就是3了。

此处直接多列举些情况:

a)f=[0, 3, 4],找1,找到的位置索引为1(从0开始),即f[1]=3

b)f=[0, 3, 4],找4,找到的位置索引为2,即f[2]=4

c)f=[0, 3, 4, 4]?,找4,找到的位置索引为2,即f[2]=4(最左边的4哦),但是,但是注意了!f其实不可能出现这种情况,正因为我们会在f=[0, 3, 4]找4的时候返回位置2,所以我们不会再新增一个4,只会用4更新4(你也可以认为啥也没变),所以更新完了仍然是f=[0, 3, 4]。我只是在此阐述一下严格的查找逻辑。

d)官方题解里面说先找到小于它的最大值f[j0],然后去更新f[j0+1],其实是一样的,正如c)中所说,f中不会有重复值。

第4步:

第5步:

第6步:

第7步:

好,结束了,f最终为[0, 1, 2, 7],所以最长严格递增子序列的长度就是4。(我说“所以”,只是按照题解的说明说的,并不是在敷衍啊。。。,我的解释还在后面)

另外,你确实也找不到更长的严格递增子序列啦,比如0 3 6 7,0 1 2 7都是4。

你可能会问,f最终为[0, 1, 2, 7],是不是代表[0, 1, 2, 7]就是最长严格递增子序列?那还真不一定

我立马就给你举个反例,比如nums=[0,3,1,6,7,2,2],最终画出来还是一样的图

得到的自然是一样的f=[0, 1, 2, 7],但是明显[0, 1, 2, 7]不是最长严格递增子序列,当然喽此时最长严格递增子序列长度仍然是4,只是这次的答案只有:[0, 3, 6, 7]

你可能会问,那是不是上图的第一行就一定是最长严格递增子序列?那仍然还真不一定

我再给你举一个反例,比如nums=[6,0,3,1,2,2,7],第1行的6 3 2 7明显不是,不过提前透露一下,判断它是不是的办法十分简单,那就是如果它不是,它就不是【狗头】。

别急,我说“如果它不是”的意思就是,直接判断它是不是一个严格递增序列,只要它是,那它就是最长严格递增子序列。那如果它真不是,那我仍然想知道谁是,咋办呢,办法也很简单,就是从右往左,从上往下,挨个找,找到的第一个比右侧小的值,就在严格递增序列之中

最后一列那选7肯定没错,第3列2也OK,第1列3不行,往下找,1行(不是1 hang, 是1 xing哦),就它了,第0列6不行,0行,就它了,所以0 1 2 7必然是最长严格递增子序列,并且最终你会发现,这么找的算法复杂度还很低,它就是O(n),原因很简单,上图中的所有元素就是原nums中的元素,一共就n个,顶多把每个遍历一遍。至于为什么这样找就能找出来,下面再详述。

二。进一步分析

先言归正传,为什么最长严格递增子序列的长度,就是f的最终长度呢?

继续研究一下刚才的实操过程,再贴一下,nums=[0,3,1,6,2,2,7]

比如第4步,

此时我们已经处理完的元素为0 3 1 6,这里面的最长严格递增子序列只有0 1 6或者0 3 6,注意上图6放的位置,6一定在第2列(从第0列开始算),3一定在第1列,0一定在第0列,其实还有一个1,它也一定在第1列。虽然这里只有nums的前4个元素,但只要把局部想明白了,后面所有的元素都是按照这个规则来堆的。

之前说到,怎样才能从上图的这堆数字中找出最长严格递增子序列,而不是只知道其长度呢?

为什么不能简单地根据从左往右的顺序来取?原因很简单,比如上图的1,他虽然处于第1列,6处于第2列,但这并不代表在nums中它一定在6的左边,我立马给你找个反例:

nums=[0, 3, 6, 1],堆出来的f图跟上图一模一样,可是在nums中1明显在6的右边。所以问题就是,f图中不能直接看出所有数字在nums中的先后顺序

如果你能知道摆放这些数字的先后顺序,那你就确切地找出所有可能的最长严格递增子序列。

即,比如我记住了,先放0,其次放3,再次放1,最后放6,那自然0 3 6, 0 1 6都是ok的,当然实际上代码中不会记住这件事。但是有一件事我们是能确定的,那就是在放6之前,第0列,第1列都有人了!否则6也跑不到第2列来,其实串起来说就是,第1列中有一个元素x,它一定在6之前就放进来了,并且它比6小,第0列中有一个元素y,它一定在x之前就放进来了,并且它比x小。

你可能有几点疑惑:

a)为啥一定会有y<x<6?

正如上面所说,虽然我记不住f图的堆放顺序,但是它一定有这么个过程啊,所以y和x是客观存在的,只是你不能一眼看出它们是谁。

你要是没明白,我再废话几句,回忆一下摆放过程,6为什么放在第2列,因为它比第1列的某个元素x大(同时如果6这一列在6之上还有元素z的话,那6自然还满足,它小于等于z),这里的x就是1(虽然3也比6小,但实际6是跟1比较的),1为什么在第1列,因为它比第0列的某个元素y大,这里的y就是0。

b)你为啥说有一个元素y,有一个元素x,跟6在同一行的0和3不就是的吗?

正如你所问,第0行的元素,摆放顺序必然是0在3之前,3在6之前。但是,第0行的元素它未必是一个递增序列,比如[6, 0, 1, 3],其f图如下,很明显,6 1 3它不行,0 1 3它才行

等等,我问的是同一行,你说第0行?em....这个坑请见文章第四大点

c)你为啥要强调有这么一个顺序,并且y<x<6?

因为y,x,6它就是当前的最长的严格递增子序列啊!

别跟上图搞混啊,我把对应图再放一下

d)嗯,我能明白y x 6一定是一个严格递增子序列了,但我想不明白它为什么一定是最长的?

如果不能证明这一点,那我这帖子仍然是在敷衍。所以到了最关键的步骤了。

另外,我们再确认一下,你怀疑最长严格递增子序列长度>=3,起码3的下限我们是确定了!

现在让我们抛开杂念【狗头】,不要管y,x具体是什么,但是6它是具体的,因为它就是我们当前正在摆放的元素,如下图,现在的实际情况就是,我们按照摆放规则,6就是摆在了第2列(从0列开始),但是你怀疑存在某个以当前这个6结尾的严格递增子序列,它的长度大于3,也就是说存在一个z,它能插到6的前面。我们逐个分析有没有这种可能性。

(1)这个序列是y x z 6

z明显在x之后再往图上摆放,如果当时x这一列下面没东西,末端就是x,那么会因为z大于x,所以z放到第2列末端。如果x这一列下面还有东西,末端是xmin,那还是一样啊,z更是大于xmin了。

那问题是如果z放到了第2列,那再轮到放6的时候,它怎么可能仍然放在第2列?它只可能放到3列了

(2)同样的道理,y z x 6,z y x 6都是不行的,与上图矛盾。

(3)这个序列是y x 6 z

开玩笑,我们现在处理的当前元素是6,后面的还没处理到,一个个来。我们现在要确定的是因为当前元素摆在第2列(从0列开始),所以以当前元素结尾的最长严格递增子序列的长度就是3。

所以说,其实我们每往图上摆一个元素,它摆在哪一列,就说明了以这个元素结尾的最长严格递增子序列的长度,就是对应的列数(从0列开始算的话,自然要加1啦)

等我们把所有元素都摆完了,那么最大的列数,自然就是真正的最长严格递增子序列的长度了。

(4)这个序列是y x z1 z2,它是4,比y x 6长呢,并且z1,z2在nums中排在6之前

完全有可能,比如nums=[0,3,1,7,8,6],f图如下,6仍然在第3列,但是在摆放6的时候,当前最长严格递增子序列的长度就已经是4了,而不是3.

但是注意,我们并没有说错,正如第3点中所说,是以6结尾的最长严格递增子序列长度为3.

我为什么之前说的时候没有加上“以6结尾”这个前缀,我先把之前的图放一下

因为当时的这个图,一共就3列,并且当时还没有到强调这个前缀的时候。所以重点就是第3点中的那段红字,可以再回顾一下,它就是证明的关键了!

三。上代码

注,由于原题只需要计算长度,不需要找出序列,所以代码中其实跟题解的逻辑是一样的,即只更新f值,而不是把所有数字摆成牌堆。上述的牌堆只是为了理解原理。

from typing import List

import bisect

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        piles = [nums[0]]

        for i in range(1, len(nums)):
            if piles[-1] < nums[i]:
                piles.append(nums[i])

            find_index = Solution.bisect_left(piles, nums[i])
            # find_index = bisect.bisect_left(piles, nums[i])
            piles[find_index] = nums[i]

        return len(piles)

    @staticmethod
    def bisect_left(a, x, l=0, h=-1):
        if h == -1:
            h = len(a)

        while l < h:
            mid = (l + h) // 2
            if x > a[mid]:
                l = mid + 1
            else:  # elif x <= a[mid]
                # 这里为什么不是h = mid - 1,因为如果找不到x,则找大于x的最小值,即右边界我们可能是需要的
                # 为什么把x == a[mid]的分支也合到这里面?其实只是想跟bisect.bisect_left逻辑保持一致而已,即如果存在重复的x,则返回最左边的x
                # 实际上本题不可能有重复的x,完全可以在找到x后立马返回,能稍微快那么一点点
                h = mid

        return l


if __name__ == '__main__':
    nums = [0, 3, 1, 6, 2, 2, 7]
    print(Solution().lengthOfLIS(nums))

四。那序列到底怎么找出来?

前面提过找的方法,再总结一下

从右往左找,第3列(从0列开始),就选第0行的7

第2列,第0行2小于7,OK,就它了

第1列,第0行3大于2,不行,第1行1小于2,OK,就它了

第0列,第0行6大于1,不行,第1行0小于1,OK,就它了(对,你可能发现了,虽然第1列取的1已经是第1行了,但是第0列仍然从第0行开始找

为什么这么找一定行呢?

我们换个例子,nums=[3,8,7,4,5,1]

很明显,3 4 5就是最长严格递增子序列,这是我们直接看nums得出的。但是在f图上,怎么才能确定4是在5之前的呢?正如我们之前所说,你既然想从f图中获取信息,那就请想象,5之前一定有一个x比5小,这是第一个已知条件。条件二是:8绝对在5之前,这是无疑的,因为它们在第0行

可惜现在8比5大,假设紧接着8下面的那个元素z比5小,请问z是不是一定在5之前,答案是一定!

有图好分析,现在把z标出来,假设z比5小,但是z在nums中就是排在5之后,有没有可能?没可能!假设z真的排在5之后,那z下面的4也肯定排在5之后,那问题来了,5到底是因为谁而来到了第2列(从0列开始),兄弟你排错队了啊!所以如果z比5小,它绝对排在5之前。
当然喽,我们看z的时候发现它其实是7,它虽然排在5之前,但它不顶用啊,同理的逻辑继续往下找,找到的第1个小于5的元素,绝对排在5之前。

问题1:

你刚才说如果z比5小,则它一定在5之前。但你好像没说,如果z大于等于5的话,它也一定在5之前啊?

它当然还是在5之前啦,因为它下面绝对有一个比5小的new_z,就是那个4啦,4在5之前,那z还不在5之前?

问题2:

现在发现4在第2行了(从第0行开始),那处理第0列的时候,还是从第0行开始吗?

自然得从第0行开始,其实我刚留了一个坑没说明白,我说8一定在5之前,是因为它们都在第0行,注意,不是因为它们是同一行,而是第0行。0乃创世之初,从左到右遵循先创与后创,其它行则不然。所以我们只有确定第0行不行的时候,才能往下找。

具体到第0列就是,我们只有发现3大于等于4的时候,才能去找1。当然喽,3实际小于4,所以我们没证据表明1是行的。我们只有证据表明,3是行的。

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

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

相关文章

PPT 快捷键使用、技巧

前言&#xff1a; 本文操作是以office 2021为基础的&#xff0c;仅供参考&#xff1b;不同版本office 的 ppt 快捷键 以及对应功能会有差异&#xff0c;需要实践出真知。 shift 移动 水平/垂直 移动 &#xff1b; shift 放大/缩小 等比例放大 缩小 &#xff1b; 正圆 正…

Python编程:01-基本数据类型-数值字符串,列表与元组,字典,集合set

python的数据类型有如下&#xff1a; 1、数字 数字类型是python中常用的类型&#xff0c;她是不可变的&#xff0c;创建一个数字很简单可以用一个变量来接收它 num12 在这里插入代码片 #创建变量num1 num29 #创建变量num2数字的类型分为如下几类&#xff1a; 整型&#x…

再谈QT的界面开发 - QT的社区版本的获取 - 2024-09

前言&#xff1a; QT的跨平台特性&#xff0c;赋予了QT的生命。2024年&#xff0c;因为项目的原因&#xff0c;重新开启了一个基于QT的跨平台项目。 QT有付费的版本和社区的版本。 1 获取社区的版本&#xff1a; 1.1 社区的版本的软件授权说明&#xff1a; Qt - Obligation…

Spring Boot 2.x基础教程:实现文件上传

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 文件上传的功能实现是我们做Web应用时候最为常见的应用场景&#xff0c;比如&#xff1a;实现头像的上传&#xff0c;Excel文件数据的导入等功能&#xff0c;都需要我们先实现文件的上传&#xff0c;然…

【含文档】基于Springboot+微信小程序 的高校心理咨询系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

WordPress最佳恶意软件扫描插件:入门级指南

在现代互联网环境中&#xff0c;网站安全已经成为每个网站管理员必须重视的问题。特别是对于使用WordPress的用户来说&#xff0c;由于其普及度高&#xff0c;WordPress网站常常成为黑客的首要攻击目标。幸运的是&#xff0c;有许多优秀的恶意软件扫描插件可以帮助我们保护网站…

案例精选 | 海门北部新城医学综合体智能化日志管理系统部署

海门北部新城医学综合体&#xff0c;即海门中医院新院区&#xff0c;坐落于江苏省南通市海门区北部新城的核心地带&#xff0c;是一座全新的现代化三级甲等中医医院。医院于2024年初正式启用&#xff0c;占地约64710平方米&#xff0c;拥有超过12万平方米的建筑面积&#xff0c…

【Python】The Algorithms:开源算法的宝库

The Algorithms 是一个开源项目&#xff0c;旨在为开发者提供各种编程语言的算法实现。该项目汇集了数千种算法的实现&#xff0c;涵盖了数据结构、排序算法、数学算法、机器学习、密码学等领域。通过该平台&#xff0c;开发者可以学习、理解并应用不同编程语言中的算法&#x…

企业安全策略制定

如今&#xff0c;网络安全是所有组织的必需品&#xff0c;而不是奢侈品。现代企业面临着针对其数据、网络和系统的复杂且不断演变的威胁。 即使一个漏洞也可能导致严重违规、财务损失和声誉受损。正如堡垒依靠多层防御共同作用一样&#xff0c;公司的安全措施必须作为一个整体…

MAC M1 安装brew 配置环境变量,安装dart

一. 下载 brew 1. 终端输入 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 2. 如遇到下载失败情况&#xff0c;需要VPN/代理 curl: (7) Failed to connect to raw.githubusercontent.com port 443 after 8 m…

Vue3 取消密码输入框在浏览器中自动回填

浏览器默认会对用户提交表单行为进行监控&#xff0c;若发现type属性值为password的input控件&#xff0c;且该控件可见时&#xff0c;会提示用户是否记住密码 VUE3登录禁止浏览器记住密码_vue3禁止chome浏览器提示保存密码-CSDN博客 项目需求&#xff1a; 前端禁止在浏览器coo…

优青博导团队携手提供组学技术服务、表观组分析、互作组分析、遗传转化实验、单细胞检测等全方位生物医学支持

&#x1f31f; 教授团队领衔&#xff0c;全方位服务&#xff01; &#x1f680; 从实验设计到论文发表&#xff0c;一站式解决方案&#xff01; &#x1f4c8; 选择我们&#xff0c;加速您的科研进程&#xff0c;让成果不再等待&#xff01; &#x1f4dd; 专业分析 定制服…

python贪吃蛇小游戏

1.简介 使用了turtle库来创建图形界面&#xff0c;你可以使用键盘的W、A、S、D键来控制蛇的移动方向。蛇吃到食物后&#xff0c;身体会增长&#xff0c;如果蛇撞到自己或者游戏边界&#xff0c;游戏就会结束。 2. 代码 import turtle import time import randomdelay 0.1# …

在MacOS上安装MongoDB数据库

一、安装方法 1.1 安装包安装 首先&#xff0c;打开MongoDB 官网下载安装包&#xff0c;下载链接&#xff1a;https://www.mongodb.com/try/download/community。 根据自己的系统环境自行选择下载的版本。将下载好的 MongoDB 安装包解压缩&#xff0c;并将文件夹名改为 mon…

数据结构:链表算法题

目录 题1.删除链表中的某个元素val题目表述&#xff1a;思路1:在源链表中进行删除更改思路2:创建一个新链表 题2:反转一个链表问题描述&#xff1a;思路1:在源链表内部进行操作思路2:创建一个新链表 题3:寻找链表中间位置题目描述:思路1:思路2:快慢指针 题1.删除链表中的某个元…

1.1.5 计算机网络的性能指标(下)

时延&#xff1a; 指数据从网络的一端传送到另一端所需的时间。有时候也称为延迟或迟延。 总时延发送时延传播时延处理时延排队时延 发送时延&#xff1a; 又名传输时延&#xff0c;节点将数据推向信道所花的时间 数据长度/发送速率 传播时延&#xff1a; 电磁波在信道…

Windows电脑本地安装DrawDB数据库设计工具并一键发布公网远程使用

文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 我们在开发项目时很多时候都会使用到数据库&#xff0c;所以选择一个好用的数据库设计工具会让工作效率翻倍。在当今数字化时代&#xff0c;数据库管理是许多企业…

git clone或repo init 时报错:fatal: 协议错误:错误的行长度 xxx

执行repo init或git clone时报错:protocol error: bad line length 或协议错误:错误的行长度 系统版本:Ubuntu20.04 repo version v2.47 repo launcher version 2.45 git version 2.25.1 报错信息 fatal: 协议错误:错误的行长度 948 fatal: 远端意外挂断了 repo: err…

一篇文章让你秒懂MySQL中的各种锁

目录 一、序言二、各种锁详细介绍1、Shared and Exclusive Locks(共享锁和读占锁)2、Intention Locks(意向锁)3、Record Locks(记录锁)4、Gap Locks(间隙锁)5、Next Key Locks (Next Key锁)6、Insert Intention Locks (插入意向锁)7、AUTO-INC Locks (自增锁)8、Predicate Lock…

java 生成.h文件,java调用c语言dll动态链接库流程

** 1、首先创建一个java文件&#xff0c;里面最好不要有中文 ** /*** BelongsPackage: PACKAGE_NAME* Author: wangqian* CreateTime: 2024-09-27 18:42:24* Describe:*/ public class testDll {static {System.loadLibrary("testDll");}public native int add(…