算法27:最长公共子序列——样本模型(4)

news2024/11/25 13:57:10

目录

简介

题目:

思路:

递归版本:

根据递归 分析推导

动态规划版本:


简介

前面刷了几道题目,都是从暴力递归到递归+动态规划的版本,最后演变成纯动态规划的版本。接下来的题目,将会跳过 递归 + 动态规划的版本。而是转头直接分享递归版本和纯动态规划版本。其实,动态规划是有技巧的,只要递归版本写的好,直接改成动态规划是手到擒来的一件事情,非常的简单。而动态规划,也是分模型的。即从左到右模型,范围模型,样本模型,业务模型。以后刷题的时候,可以直接套用这些模型,思路会清晰很多。

今天,我们来分析 样本模型。而样本模型的套路,就是直接套路样本数据的最后一个元素的无限可能,即存在的各种各样的可能性。以样本数据的最后一个元素即基础,进行分析。

题目:

https://leetcode.com/problems/longest-common-subsequence/

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

思路:

1. 如果样本数据都不存在,即无效的样本。

2. 如果一个样本长度为1,另一个样本很长,则长度最多为1,也有可能为0;

3. 如果2个样本数据都很长,那就逐层讨论。每次递归,都以当前样本的最后一个元素,讨论样本存在的可能性。最后把所有的样本数据拿出来比较,找到符合条件的数据即可,

递归版本:

package code03.动态规划_07;

/**
 * 链接:https://leetcode.com/problems/longest-common-subsequence/
 *
 * 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
 *
 * 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
 *
 * 给你一些样本数据,要求找出共性的,就属于样本模型。 而样本模型的核心,就是以最后一个字符为基础进行讨论
 */
public class LongestCommonSubsequence_04_opt {

    public static int longestCommonSubsequence(String text1, String text2)
    {
        if (text1 == null || text2 == null || text1.isEmpty() || text2.isEmpty()) {
            return 0;
        }

        char[] s1 = text1.toCharArray();
        char[] s2 = text2.toCharArray();

        //以s1做行,s2做列
        int[][] dp = new int[ s1.length][s2.length];

        int index1 = s1.length - 1;
        int index2 = s2.length - 1;

        //根据递归   if (index1 == 0 && index2 == 0)  而来
        dp[0][0] = s1[0] == s2[0] ? 1 : 0;

        //根据递归  else if (index1 == 0)  而来, 此处代表先处理 列
        for (int i = 1; i <= index2; i++) {
            dp[0][i] = s1[0] == s2[i] ? 1 : dp[0][i-1];
        }

        //根据递归  else if (index2 == 0)  而来, 此处代表先处理 行
        for (int j = 1; j <= index1; j++) {
            dp[j][0] = s1[j] == s2[0] ? 1 : dp[j-1][0];
        }

        //通用case  根据递归中最后一个else而来
        for (int row = 1; row <= index1; row++) {
            for (int col = 1; col <= index2; col++) {

                //根据 int p1 =  process(s1, s2, index1, index2-1) 改写
                int p1 = dp[row][col - 1];

                //根据 int p2 =  process(s1, s2, index1-1, index2) 改写
                int p2 = dp[row -1][col];

                //int p3 = s1[index1] == s2[index2] ? (process(s1, s2, index1-1, index2-1) + 1) : 0;
                int p3 = s1[row] == s2[col] ? (dp[row -1][col -1] + 1) : 0;

                dp[row][col] = Math.max(p1, Math.max(p2, p3));
            }
        }

       //返回值对应递归中的下标
       return dp[index1][index2];
    }

    public static void main(String[] args) {
        String s1 = "abcde";
        String s2 = "ace";

        System.out.println(longestCommonSubsequence(s1,s2));
    }
}

递归好不好,是动态规划的关键。如果递归写不好,那么动态规划也就会非常的复杂,甚至完全没有版本写出来。因此,尝试写好一个递归,才是动态规划的关键所在。

根据递归 分析推导

1. 假设样本1数据为 ace 样本2数据为abcde得到的结果应该是3

2. 以样本1作行,样本2作列绘制二维数组。可得到以下表 (样本1或2,即可作行,也可左列)

a(0)b(1)c(2)d(3)e(4)
a(0)
b(1)
c(2)

3. 根据递归内部

if (index1 == 0 && index2 == 0) {
    return s1[index1] == s2[index2] ? 1 : 0;
}

可知数组dp[0][0] 为1或者0.  而样本开头都是a,因此此位置为 1

a(0)b(1)c(2)d(3)e(4)
a(0)1
b(1)
c(2)

4. 根据递归内部

else if (index1 == 0) { //如果数组1以当前下标结尾,而数组2不确定 对应解题思路2. 如果一个样本长度为1,另一个样本很长,则长度最多为1,也有可能为0;

    //如果当前下标的字符数组相等,此时text1已经结束了并且找到了和text2相等的字符,直接返回1.
    //如果没找到,继续找text2的字符
    return s1[index1] == s2[index2] ? 1 : process(s1, s2, index1, index2-1);

由于index1对应的是样本1的数据,即行数据。而index2对应的是样本2数据,即列数据。此处列的下标对应的为0,也就是说此处可以推导出行下标为0对应的所有的列。如果相等则为1, 不相等则继续递归,依次类推。

0行  0 列已经推导出为 1,即 dp[0][0] = 1;

a != e 依赖index2 - 1

a != d 依赖 index2 - 1

a != c 依赖 index2 - 1

a != b 依赖 index2 - 1

a == a 得到1,所以第一行都为1

a(0)b(1)c(2)d(3)e(4)
a(0)11111
b(1)
c(2)

5. 根据递归内部:

else if (index2 == 0) { //数组2来到结尾,数组1不一定。 对应解题思路2. 如果一个样本长度为1,另一个样本很长,则长度最多为1,也有可能为0;

    //原理同上。此时继续往下找的是字符数组text1
    return s1[index1] == s2[index2] ? 1 :process(s1, s2, index1-1, index2);
}

推导方法一样:

a != c 依赖上一行 index1 - 1

a != b  依赖上一行 index1 - 1

a == a 之前推导过的是1, 因此,此列都为1

a(0)b(1)c(2)d(3)e(4)
a(0)11111
b(1)1
c(2)1

6. 最后推导

//以第一个数组结尾结束,第二个不确定。 就是上方的index1==0的过程
int p1 =  process(s1, s2, index1, index2-1);

//以第二个数组结尾结束,第一个不确定。 index2==0的过程
int p2 =  process(s1, s2, index1-1, index2);

//同时以数组1的当前下标index1  和 数组2的当前下标index2结尾。
//数组1为 ab123c45d
//数组2位 123abc45d
//最长公共子序为 12345
//等价与上方的case index1 == 0 && index2 == 0
int p3 = s1[index1] == s2[index2] ? (process(s1, s2, index1-1, index2-1) + 1) : 0;

return Math.max(p1, Math.max(p2, p3));
a(0)b(1)c(2)d(3)e(4)
a(0)11111
c(1)112
e(2)1

a(0)b(1)c(2)d(3)e(4)
a(0)11111
c(1)11222
e(2)1122

                最后一步: p1 依赖上一列  p2 依赖前一列,p3是判断是否相等。如果相等,则是获取

                上一行和上一列的max值,并且加 1。 即得到 2 + 1 = 3;

a(0)b(1)c(2)d(3)e(4)
a(0)11111
c(1)11222
e(2)11223

由于递归传入的参数是: process(s1, s2, text1.length()-1, text2.length()-1); 所以最终得到的数据是dp[2][4], 即3.  由此,我们设计动态规划的代码:

动态规划版本:

package code03.动态规划_07;

/**
 * 链接:https://leetcode.com/problems/longest-common-subsequence/
 *
 * 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
 *
 * 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
 *
 * 给你一些样本数据,要求找出共性的,就属于样本模型。 而样本模型的核心,就是以最后一个字符为基础进行讨论
 */
public class LongestCommonSubsequence_04_opt {

    public static int longestCommonSubsequence(String text1, String text2)
    {
        if (text1 == null || text2 == null || text1.isEmpty() || text2.isEmpty()) {
            return 0;
        }

        char[] s1 = text1.toCharArray();
        char[] s2 = text2.toCharArray();

        //以s1做行,s2做列
        int[][] dp = new int[ s1.length][s2.length];

        int index1 = s1.length - 1;
        int index2 = s2.length - 1;

        //根据递归   if (index1 == 0 && index2 == 0)  而来
        dp[0][0] = s1[0] == s2[0] ? 1 : 0;

        //根据递归  else if (index1 == 0)  而来, 此处代表先处理 第一行的所有列
        for (int i = 1; i <= index2; i++) {
            dp[0][i] = s1[0] == s2[i] ? 1 : dp[0][i-1];
        }

        //根据递归  else if (index2 == 0)  而来, 此处代表先处理 第一列的所有行
        for (int j = 1; j <= index1; j++) {
            dp[j][0] = s1[j] == s2[0] ? 1 : dp[j-1][0];
        }

        //通用case  根据递归中最后一个else而来
        for (int row = 1; row <= index1; row++) {
            for (int col = 1; col <= index2; col++) {

                //根据 int p1 =  process(s1, s2, index1, index2-1) 改写
                int p1 = dp[row][col - 1];

                //根据 int p2 =  process(s1, s2, index1-1, index2) 改写
                int p2 = dp[row -1][col];

                //int p3 = s1[index1] == s2[index2] ? (process(s1, s2, index1-1, index2-1) + 1) : 0;
                int p3 = s1[row] == s2[col] ? (dp[row -1][col -1] + 1) : 0;

                dp[row][col] = Math.max(p1, Math.max(p2, p3));
            }
        }

       //返回值对应递归中的下标
       return dp[index1][index2];
    }

    public static void main(String[] args) {
        String s1 = "ace";
        String s2 = "abcde";

        System.out.println(longestCommonSubsequence(s1,s2));
    }
}

试想一下,如果没有递归做辅助,这些动态规划的代码该如何写。甚至写好了,我们完全搞不清楚这些代码的意思。

为啥

dp[0][0] = s1[index1] == s2[index1] ? 1 : 0;

为啥要求以下三种情况的最大值

//根据 int p1 =  process(s1, s2, index1, index2-1) 改写
int p1 = dp[row][col - 1];

//根据 int p2 =  process(s1, s2, index1-1, index2) 改写
int p2 = dp[row -1][col];

//int p3 = s1[index1] == s2[index2] ? (process(s1, s2, index1-1, index2-1) + 1) : 0;
int p3 = s1[row] == s2[col] ? (dp[row -1][col -1] + 1) : 0;
dp[row][col] = Math.max(p1, Math.max(p2, p3));

为啥最后要返回的值 下标 是下列这样的

return dp[index1 - 1][index2 - 1];

没有递归做辅助,这一些的为啥可能根本就解释不清楚。更别谈写出动态规划的代码了。

由于这是一道力扣原题,结果是动态规划版本可以顺利通过,而递归版本一直是超时,这也能够说明不同的业务场景,不用的写法,是非常大重要的。

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

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

相关文章

chatgpt赋能python:Python找出列表中出现最多的元素

Python找出列表中出现最多的元素 介绍 在Python的编程过程中&#xff0c;经常需要处理列表&#xff0c;而处理列表时最常见的问题之一就是如何找出列表中出现最多的元素。在某些情况下&#xff0c;我们可能需要确定列表中重复出现最多的元素&#xff0c;并将其提取出来。Pyth…

[机器学习]线性回归

准备入门一下机器学习算法。 今天学习了线性回归&#xff0c;都是理论的东西&#xff0c;没有对于代码的实现&#xff0c;代码也会跟着进度好好搞一下。 对于线性回归的基础概念&#xff0c;我感觉很依靠概率论和线性代数两门课&#xff0c;作为刚准备完数学一考研的我&#xf…

Systrace系列12 —— CPU Info 解读

本文主要是对 Systrace 中的 CPU 信息区域(Kernel)进行简单介绍,简单介绍了如何在 Systrace 中查看 Kernel 模块输出的 CPU 相关的信息,了解 CPU 频率、调度、锁频、锁核相关的信息。 CPU 区域图例 下面是高通骁龙 845 手机 Systrace 对应的 Kernel 中的 CPU Info 区域(底下…

人工智能轨道交通行业周刊-第46期(2023.5.22-5.28)

本期关键词&#xff1a;数字孪生、AI铁路人、道岔、施封锁、图像质量评价、大模型小型化 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道…

在VIVADO下烧写ZC706板载FLASH的操作步骤

1&#xff0c;原理图分析 首先看原理图&#xff0c;我们兼容ZC706的板子有两片 FLASH&#xff0c;型号是S25FL128A,连接方式如下&#xff1a; 可以看到两片是分别接在了XC7Z045芯片的引脚上&#xff0c;是互不相干的并联方式&#xff0c;每个FLASH芯片支持X4模式&#xff0c;也…

Systrace系列11 —— Triple Buffer 解读

本文主要是对 Systrace 中的 Triple Buffer 进行简单介绍,简单介绍了如何在 Systrace 中判断卡顿情况的发生,进行初步的定位和分析,以及介绍 Triple Buffer 的引入对性能的影响。 怎么定义掉帧? Systrace 中可以看到应用的掉帧情况,我们经常看到说主线程超过 16.6 ms 就会…

第一个Vue程序

什么是MVVM MVVM是Model-View-ViewModel的缩写&#xff0c;是一种软件架构模式&#xff0c;用于将用户界面&#xff08;UI&#xff09;的开发与业务逻辑和数据分离开来。 在MVVM架构中&#xff0c;Model代表数据模型层&#xff0c;View代表用户界面层&#xff0c;ViewModel充…

基于Java+控制台实现教材管理系统

基于Java控制台实现教材管理系统 一、系统介绍二、功能展示1.教材订购2.教材出售3.教材统计4.库存管理 四、其它1.其他系统实现2.获取源码 一、系统介绍 系统主要包括了教材订购、教材出售、教材统计、库存管理几大部分&#xff1b; 其中功能主要包括&#xff1a; 一、教材订购…

English Learning - L3 作业打卡 Lesson3 Day22 2023.5.26 周五

English Learning - L3 作业打卡 Lesson3 Day22 2023.5.26 周五 引言&#x1f349;句1: He would never pour salt on a wound, or make someone feel worse about something that was already a painful experience.成分划分弱读连读爆破语调 &#x1f349;句2: However, some…

字符串最后一个单词的长度

描述 计算字符串最后一个单词的长度&#xff0c;单词以空格隔开&#xff0c;字符串长度小于5000。&#xff08;注&#xff1a;字符串末尾不以空格为结尾&#xff09; 输入描述&#xff1a; 输入一行&#xff0c;代表要计算的字符串&#xff0c;非空&#xff0c;长度小于500…

JavaScript处理移动web交互

touch对象和touchevent touch事件 touch对象 每一次发生touch事件时就会产生一个touch对象&#xff0c;类似事件处理函数中的事件对象。 <div class" "><button class"child" style"height: 400px; width: 400px">我是按钮</b…

OneNote:隐藏OneNote笔记右边的作者和更新时间

OneNote在其他电脑登录后同步笔记&#xff0c;会在笔记右边显示用户名称和更新时间&#xff0c;消除方法如下&#xff1a; 在顶部找到历史记录&#xff0c;点击隐藏作者即可&#xff1a; 隐藏后效果&#xff1a; 说明&#xff1a; 1、用于window10系统。

Mybatis源码的理解

文章目录 0.核心的包1.1 配置文件mybatis-config.xml1.2 配置文件解析将配置文件转化为输入流,将 xml转化Configuration类.解析配置对应的标签为Configuration的属性Configuration的核心类的属性 1.3 解析完成查询之后的configurationenvironment类sqlFragments类mapperRegistr…

(转载)基于量子遗传算法的函数寻优算法

8.1 理论基础 8.1.1 量子遗传算法概述 量子遗传算法(quantum genetic algorithm,QGA)是量子计算与遗传算法相结合的产物&#xff0c;是一种新发展起来的概率进化算法。遗传算法是处理复杂优化问题的一种方法&#xff0c;其基本思想是模拟生物进化的优胜劣汰规则与染色体的交…

结构型设计模式02-代理模式

✨作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 代理模式 1、不使用代理模式 举例说明&#xff1a;小明喜欢一个女生&#xff08;小红&#xff09;&#xff0c;因为小红不认识小明…

【数据结构】初步了解排序

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 1.排序的概念及其运用 1.1排序的概念 2.常见排序算法的实现 2.1插入排序 2.2希尔排序 问题:gap是多少合适&#xff1f; 1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所…

App上架流程

准备 开发者账号完工的项目 上架步骤 一、创建App ID二、创建证书请求文件 &#xff08;CSR文件&#xff09;三、创建发布证书 &#xff08;CER&#xff09;四、创建Provisioning Profiles配置文件 &#xff08;PP文件&#xff09;五、在App Store创建应用六、打包上架 一、…

设置linux的时间

目录 一、什么是时间 &#xff08;1&#xff09;例子1 &#xff08;2&#xff09;例子2 二、什么是本地时间 三、linux设置本地时间的方法 &#xff08;1&#xff09;方式一&#xff1a;通过互联网自动同步 1.修改时间同步服务器 2.查看时间同步情况 &#xff08;2&…

自动化测试1

目录 1.什么是自动化测试 1.1自动化分类 1.1单元测试 1.2接口测试 1.3UI自动化测试 2.selenium 1.什么是selenium 2.selenium的特点 3.工作原理 3.seleniumJava 1.搭建 1.查看Chrome版本 2.下载Chrome浏览器驱动 3.配置,放到该目录下 2.验证是否搭建成功 1.什么是…

springboot 集成jwt的使用

1.JWT介绍 JWT&#xff08;JSON Web Token&#xff09;是一种基于JSON格式的身份验证和授权标准&#xff0c;经常用于Web应用程序中的用户身份验证。JWT由三个部分组成&#xff1a;头部、载荷和签名。头部包含算法和类型信息&#xff0c;载荷包含用户数据和元数据&#xff0c;…