最长公共子序列(LCS)与最长上升子序列(LIS)问题的相互转换

news2024/11/15 17:29:09

在此只做直观理解,不做严格证明
参考:LCS 问题与 LIS 问题的相互关系,以及 LIS 问题的最优解证明

LCS转LIS

LCS转LIS只能对特殊情况适用。即当LCS中两个数组有一个不存在重复元素的情况下才能进行转换。
我们以一个例子进行说明,假设有如下两个数组A和B, A不存在重复元素:
A:

1
7
8
a
b
c

B:

1
6
7
8
c
d
e

假设A和B的公共子序列为 [1, 8, 7]。其中a,b,c,d,e都是一些不影响上述假设的数字,为了更容易理解,这里用字母代替。
我们把B中的元素都换成在A中的下标,因为A[1] = 1, A[3] = 8, A[5]=7, 即得到一个新的数组C如下:
C:

1
3
5
3
?
?
?

对C求最长上长子序列,得到[1, 3, 5]。再它再映射到A中,即最终得到序列:
[A[1], A[3], A[5]] = [1, 8, 7]。我们发现通过LIS求得的序列与LCS相同。

LIS转LCS

同样使用一个例子说明
假设有数组A如下:

1
3
5
7
-1
3

显然,最长上升子序列为 [1, 3, 5, 7]。我们对它做一个排序,得到数组B:

1
3
3
5
7
-1

求A,B的最长公共子序列得 [1,3,5,7]。与LIS求得的结果相同。

直观理解就是:LIS相互之间其实是有序的,排序后并不会破坏这种顺序。而且排序后一定不存在更长的LCS,如果有的话,那它一定比LIS更长,矛盾!

LCS的解法

使用动态规划,以leetcode 为例: LCR 095. 最长公共子序列
。假设有s[1…m]和t[1…n]两个字符串序列,用dp[i][j] 表示s[1…i]和t[1…j]的最长公共子序列的长度。则状态转移方程如下:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] i f   s i = t i m a x { d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] } e l s e dp[i][j] = \left\{ \begin{array}{cc} dp[i-1][j-1] & if\ s_i = t_i \\ max\{dp[i][j-1], dp[i-1][j]\} & else \end{array} \right . dp[i][j]={dp[i1][j1]max{dp[i][j1],dp[i1][j]}if si=tielse

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length();
        int n = text2.length();
        int[][] dp = new int[m+1][n+1];
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                char a = text1.charAt(i-1);
                char b = text2.charAt(j-1);
                if(a == b){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
}

时间复杂度 O ( m n ) O(mn) O(mn)
空间复杂度 O ( m n ) O(mn) O(mn), 可以用滚动数组进一步优化到O(n), 此处略

LIS 的解法

参考leetcode 300. 最长递增子序列

假设有序列a[1…n], 要求它的最长上升子序列。

朴素解法-动态规划

我们用dp[i] 表示a[1…i] 的最长上升子序列,则状态转移方程如下:

d p [ i ] = { max ⁡ j ∈ [ 1.. i − 1 ] ∧ a j < a i { d p [ j ] + 1 } i f   { j ∣ a j < a i } ≠ ∅ 1 e l s e dp[i] = \left\{ \begin{array}{cc} \max_{j\in [1..i-1]\land a_j < a_i}\{dp[j]+1\} & if\ \{j | a_j < a_i \} \neq \empty \\ 1 & else \end{array} \right . dp[i]={maxj[1..i1]aj<ai{dp[j]+1}1if {jaj<ai}=else

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = 1;
        for(int i = 1; i < n; i++){
            dp[i] = 1;
            for(int j = i - 1; j >= 0; j--){
                if(nums[j] < nums[i]){
                    dp[i] = Math.max(dp[i], 1 + dp[j]);
                }
            }
        }
        int max = 0;
        for(int i = 0; i < n; i++) {
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}

时间复杂度 O ( n 2 ) O(n^2) O(n2)
空间复杂度 O ( n ) O(n) O(n)

优化解法-动态规划+二分查找

首先定义函数 g ( i ) g(i) g(i) 表示在长度为i的所有上升子序列中,最小的最后一个元素。例如数组[1,2,7,5] 有下面两个长度为3的上升子序列: [1,2,7] 、[1,2,5]。它们最后一个元素分别为 7、5其中最小的是5,所以根据定义 g(3) = 5。

那么我们的目的就是需要构建这样一个函数。可以通过反证法证明这个函数一定是单调的。请参考:LCS 问题与 LIS 问题的相互关系,以及 LIS 问题的最优解证明。

构建的方法是不断地从原数组中读取一个数字,然后更新认知。
初始时 g 是一个空的数组,然后不断地添加或更新。假设某一个时刻读取到的数字是 a j a_j aj。因为g是单调的,因此我们可以通过二分查找找到最接近 a j a_j aj的g(i) 使得 g ( i ) < a j g(i) < a_j g(i)<aj。显然我们可以在长度为 i i i的上升子序列基础上拼接 a j a_j aj得到一个长度为 i + 1 i+1 i+1的上升子序列, 因此令

g ( i + 1 ) = { a j 如果  g ( i + 1 ) 未初始化 min ⁡ { g ( i + 1 ) , a j } 否则 g(i+1) = \left\{ \begin{array}{cc} a_j & 如果\ g(i+1) 未初始化 \\ \min\{g(i+1), a_j\} & 否则 \end{array} \right . g(i+1)={ajmin{g(i+1),aj}如果 g(i+1)未初始化否则
为了编程方便,g通常初始化为一个非常大的值,例如,Integer.MAX_VALUE, 且 g(i+1) 如果已经初始化,因为g(i)是所有小于 a j a_j aj的数中最大的,则g(i+1)一定大于等于 a j a_j aj。所以可以直接令:

g ( i + 1 ) = a j g(i+1) = a_j g(i+1)=aj

凝练成代码如下

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] g = new int[n+1]; // g[i]表示长度为i的上升子序列中最小的一个结尾元素,参考宫水三叶
        for(int i = 1; i <= n; i++){
            g[i] = Integer.MAX_VALUE;
        }
        int max = 0;
        for(int i = 0; i < n; i++){
            int l = 0;
            int r = i+1;
            while(l + 1 < r){
                int mid = (l + r) >> 1;
                if(g[mid] >= nums[i]){
                    r = mid;
                }else{
                    l = mid;
                }
            }
            g[r] = nums[i];
            max = Math.max(max, r);
        }
        return max;
    }
}

时间复杂度 O ( n log ⁡ 2 n ) O(n\log_2 n) O(nlog2n)
空间复杂度 O ( n ) O(n) O(n)

LIS的应用

以 leetcode 334. 递增的三元子序列 为例。

在这里插入图片描述
解法一:求LIS,判断LIS是否大于等于3。时间复杂度 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n), 空间复杂度 O ( n ) O(n) O(n)。代码略。
解法二:考虑到只需要判断有没有长度为3的子序列,因此在求LIS时只需要维维g(1)和g(2)即可,当发现需要赋值g(3)时,说明存在长度为3的上升子序列,直接返回true。代码如下。时间复杂度 O ( n ) O(n) O(n), 空间复杂度 O ( 1 ) O(1) O(1)

class Solution {
    // g[i] 表示长度为i的上升子序列的最小【结尾元素】 
    public boolean increasingTriplet(int[] nums) {
        int n = nums.length;
        int g1 = Integer.MAX_VALUE;
        int g2 = Integer.MAX_VALUE;
        for(int i = 0; i < n; i++){
            if(nums[i] > g2) return true;
            else if(nums[i] > g1) g2 = nums[i];
            else g1 = nums[i]; 
        }
        
        return false;
    }
}

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

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

相关文章

leetCode 76. 最小覆盖子串 + 滑动窗口 + 哈希Hash

我的往期文章&#xff1a;此题的其他解法&#xff0c;感兴趣的话可以移步看一下&#xff1a; leetCode 76. 最小覆盖子串 滑动窗口 图解&#xff08;详细&#xff09;-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/134042115?spm1001.2014.3001.5501 力…

XJ+Nreal 高精度地图+Nreal眼镜SDK到发布APK至眼镜中

仅支持Anroid平台 Nreal套装自带的计算单元&#xff0c;其实也是⼀个没有显示器的Android设备 新建unity⼯程&#xff0c;将⼯程切换Android平台。 正在上传…重新上传取消正在上传…重新上传取消 Cloud XDK Unity User Manual for Nreal ARGlasses 该XDK是针对 NReal AR 眼镜…

mysql源码安装

Linux环境 1、mysql下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/5.7.html#downloads 下载参考&#xff1a; 2、把下载的 MySQL 压缩包上传到 Linux 服务器 3、解压mysql-5.7.39-linux-glibc2.12-x86_64.tar.gz tar -zxvf mysql-5.7.39-linux-glibc2.12-x86…

【UE】Rider编辑器错误 .NET SDK 的版本 6.0300 至少需要 MBuild 的 17.00 版本,当前可用的 MSuld 版本

异常&#xff1a;.NET SDK 的版本 6.0300 至少需要 MBuild 的 17.00 版本&#xff0c;当前可用的 MSuld 版本为 16.1.2.50704请在 global.json 中指定的 .NET SDK 更为需要当前可用的 MSBuld 版本的版本 解决 切换当前使用的MBuild版本 File->Settings…打开设置窗口 找到…

基于web和mysql的图书管理系统

系统分为用户端和管理员端 用户端功能如下 登陆注册数据一栏个人信息修改个人信息修改密码图书查询借阅信息借阅状态安全退出 管理员端功能如下 登录个人信息修改个人信息修改密码读者管理书籍管理借阅管理借阅状态安全退出 用户 管理员 源码下载地址 支持&#xff1a;远程…

javaswing/gui+mysql的学生信息管理系统

使用了Java Swing作为前端界面的开发工具&#xff0c;而 MySQL 作为后端数据库管理系统。这个系统主要用于学生信息的管理&#xff0c;包括班级和学生的增删改查操作。 在系统开发过程中&#xff0c;首先设计了数据库表结构&#xff0c;包括班级表和学生表&#xff0c;并定义了…

损失函数总结(九):SoftMarginLoss、MultiLabelSoftMarginLoss

损失函数总结&#xff08;九&#xff09;&#xff1a;SoftMarginLoss、MultiLabelSoftMarginLoss 1 引言2 损失函数2.1 SoftMarginLoss2.2 MultiLabelSoftMarginLoss 3 总结 1 引言 在前面的文章中已经介绍了介绍了一系列损失函数 (L1Loss、MSELoss、BCELoss、CrossEntropyLos…

NCCL后端

"NCCL" 代表 "NVIDIA Collective Communications Library"&#xff0c;"NVIDIA 集体通信库"&#xff0c;它是一种由 NVIDIA 开发的用于高性能计算的通信库。NCCL 专门设计用于加速 GPU 群集之间的通信&#xff0c;以便在并行计算和深度学习等领域…

智能直播,助力新营销战场 !降本增效,新消费市场唾手可得

在当今竞争激烈的全球商业环境中&#xff0c;企业们迫切需要降低成本、提高效率&#xff0c;物联网&#xff08;IoT&#xff1a;Internet of Things&#xff09;的快速崛起为企业提供了全新的增长动力。它直接改变了人们的生活方式&#xff0c;其中最突出的表现就是网购&#x…

每日一题 2558. 从数量最多的堆取走礼物(简单,heapq)

怎么这么多天都是简单题&#xff0c;不多说了 class Solution:def pickGifts(self, gifts: List[int], k: int) -> int:gifts [-gift for gift in gifts]heapify(gifts)for i in range(k):heappush(gifts, -int(sqrt(-heappop(gifts))))return -sum(gifts)

2023MathorCup(妈妈杯) 数学建模挑战赛 解题思路

云顶数模最新解题思路免费分享~~ 2023妈妈杯数学建模A题B题思路&#xff0c;供大家参考~~ A题 B题

ReentrantLock 是如何实现锁公平和非公平性的 ?

公平和非公平 公平&#xff0c;指的是竞争锁资源的线程&#xff0c;严格按照请求顺序来分配锁。非公平&#xff0c;表示竞争锁资源的线程&#xff0c;允许插队来抢占锁资源。ReentrantLock 默认采用了非公平锁的策略来实现锁的竞争逻辑。 ReentrantLock ReentrantLock 内部使…

C程序设计(第五版)谭浩强

目录 目录 第1章程序设计和C语言 ​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑第2章算法——程序的灵魂 ​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编辑​编…

Java面向对象(进阶)-- this关键字的使用

文章目录 一、引子&#xff08;1&#xff09; this是什么&#xff1f;&#xff08;2&#xff09;什么时候使用this1.实例方法或构造器中使用当前对象的成员2. 同一个类中构造器互相调用 二、探讨&#xff08;1&#xff09;问题&#xff08;2&#xff09;解决 三、this关键字&am…

sql---慢查询和语句耗时

查看当前会话的所有的sql语句耗时情况 profile 开启 查询指定sql的各个阶段耗时 查看执行计划指令 Explain Explain select * from 表 Index 和 all 属于性能不太好 在不扫描得的情况下才可能为null&#xff0c;index表示使用了索引但是扫描了所有的索引&#xff…

Java 入门指南:使用 Docker 创建容器化 Spring Boot 应用程序

文章目录 步骤 1: 准备工作步骤 2: 克隆 Spring Boot 应用程序步骤 3: 创建 Dockerfile步骤 4: 构建 Docker 映像步骤 5: 运行容器步骤 6: 链接到本地数据库步骤 7: 使用 Docker Compose 运行多个容器步骤 8: 设置 CI/CD 管道结论 &#x1f388;个人主页&#xff1a;程序员 小侯…

《SpringBoot项目实战》第三篇—留下用户调用接口的痕迹

系列文章导航 第一篇—接口参数的一些弯弯绕绕 第二篇—接口用户上下文的设计与实现 第三篇—留下用户调用接口的痕迹 第四篇—接口的权限控制 第五篇—接口发生异常如何统一处理 本文参考项目源码地址&#xff1a;summo-springboot-interface-demo 前言 大家好&#xff01;…

【QT开发(17)】2023-QT 5.14.2实现Android开发

1、简介 搭建Qt For Android开发环境需要安装的软件有&#xff1a; JAVA SDK &#xff08;jdk 有apt install 安装&#xff09; Android SDK Android NDKQT官网的介绍&#xff1a; Different Qt versions depend on different NDK versions, as listed below: Qt versionNDK…

Python 算法高级篇:桶排序与基数排序

Python 算法高级篇&#xff1a;桶排序与基数排序 引言什么是桶排序&#xff1f;桶排序的基本步骤桶排序的示例 什么是基数排序&#xff1f;基数排序的基本步骤基数排序的示例 桶排序与基数排序的应用桶排序的应用基数排序的应用 Python 示例代码总结 引言 在算法高级篇的课程中…

css列表样式

html文件如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> <link href"css/style.css" rel"stylesheet" type"text/css">&…