【算法】最长公共子序列编辑距离

news2024/10/5 18:34:43

文章目录

  • 最长公共子序列(LCS)
  • 编辑距离(Edit Distance)
  • 总结
  • 相关题目练习
    • 583. 两个字符串的删除操作 https://leetcode.cn/problems/delete-operation-for-two-strings/
    • 712. 两个字符串的最小ASCII删除和 https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/
      • 解法1:求编辑距离
      • 解法2:求最长子序列
    • 1458. 两个子序列的最大点积 https://leetcode.cn/problems/max-dot-product-of-two-subsequences/
    • 97. 交错字符串 https://leetcode.cn/problems/interleaving-string/

本文记录这种两个序列之间相互比较的 dp 题目的通用模板。

最长公共子序列(LCS)

1143. 最长公共子序列

在这里插入图片描述
递推公式如下
在这里插入图片描述
为了避免出现负数下标,因此定义 dp 数组时定义成 dp[m + 1][n + 1]。

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), 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) {
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) 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];
    }
}

可以优化成一维 dp 数组。

因为 dp[i][j] 的取值只和 左上角,上面 和 左边 这三个位置的数字有关,只需要引入一个额外的变量 pre 记录一下遍历过程中被覆盖的 dp[j]作为下一次左上角的值 就好了。

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m = text1.length(), n = text2.length();
        int[] dp = new int[n + 1];
        for (int i = 1; i <= m; ++i) {
            int pre = dp[0];
            for (int j = 1; j <= n; ++j) {
                int tmp = dp[j];
                dp[j] = text1.charAt(i - 1) == text2.charAt(j - 1)? pre + 1: Math.max(dp[j], dp[j - 1]);
                pre = tmp;
            }
        }
        return dp[n];
    }
}

编辑距离(Edit Distance)

72. 编辑距离

在这里插入图片描述
先定义 dp[i][j] 表示 word1 从0~i,word2 从0~j 需要的最少操作数。

递推公式如下:
在这里插入图片描述
min() 中的三个 分别对应 删除、添加、替换 这三种操作。

class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length(), n = word2.length();
        int[][] dp = new int[m + 1][n + 1];
        // dp数组初始化
        for (int i = 0; i <= m; ++i) dp[i][0] = i;
        for (int j = 0; j <= n; ++j) dp[0][j] = j;
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
            }
        }
        return dp[m][n];
    }
}

继续优化成一维 dp 数组

class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length(), n = word2.length();
        int[] dp = new int[n + 1];
        for (int j = 0; j <= n; ++j) dp[j] = j;
        for (int i = 1; i <= m; ++i) {
            dp[0] = i;				// 注意这里的dp[0]要随着i的变化更新
            int pre = dp[0] - 1;    // 引入额外的pre记录被覆盖的dp[i-1][j-1]
            for (int j = 1; j <= n; ++j) {
                int tmp = dp[j];
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[j] = pre;
                else dp[j] = Math.min(pre, Math.min(dp[j], dp[j - 1])) + 1;
                pre = tmp;
            }
        }
        return dp[n];
    }
}

总结

这两道题目的类似点在于都是比较两个序列之间的内容,这种情况下通常定义 dp 数组为:
dp[m + 1][n + 1] (之所以 + 1 是为了方式遍历的过程中出现负数下标)

与之对应,遍历元素的时候,使用的下标分别是 i - 1 和 j - 1。(因为原始数据的数据范围还是 0 ~ m 和 0~ n)

由于无后效性,即 dp[i][j] 的数值只与和它接近的几个数字有关,因此可以优化 dp 数组的空间。

相关题目练习

583. 两个字符串的删除操作 https://leetcode.cn/problems/delete-operation-for-two-strings/

https://leetcode.cn/problems/delete-operation-for-two-strings/
在这里插入图片描述
和 72. 编辑距离 这道题目很像,唯一的区别在于,可以使用的操作只有删除任意一个字符串中的一个元素。

class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length(), n = word2.length();
        int[][] dp = new int[m + 1][n + 1];
        // dp数组初始化
        for (int i = 0; i <= m; ++i) dp[i][0] = i;
        for (int j = 0; j <= n; ++j) dp[0][j] = j;
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1;
            }
        }
        return dp[m][n];
    }
}

直接把 72. 编辑距离 这道题目的答案复制过来,删除 dp[i - 1][j - 1] 到 dp[i][j] 的转移即可(对应着这道题没有替换元素的操作)

712. 两个字符串的最小ASCII删除和 https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/

https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/

在这里插入图片描述

解法1:求编辑距离

同样是只有删除操作的编辑距离,除此之外,每次操作需要的花费变成了字符的 ASCII 码。

class Solution {
    public int minimumDeleteSum(String s1, String s2) {
        int m = s1.length(), n = s2.length();
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; ++i) dp[i][0] = dp[i - 1][0] + s1.charAt(i - 1);
        for (int j = 1; j <= n; ++j) dp[0][j] = dp[0][j - 1] + s2.charAt(j - 1);
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                char a = s1.charAt(i - 1), b = s2.charAt(j - 1);
                if (a == b) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = Math.min(dp[i - 1][j] + a, dp[i][j - 1] + b);
            }
        }
        return dp[m][n];
    }
}

解法2:求最长子序列

这一题乍一看和上一题很像! 但其实应该反过来想,这道题求的也可以是最长公共子序列,这样剩下的就是需要被删除的字符了。

class Solution {
    public int minimumDeleteSum(String s1, String s2) {
        int m = s1.length(), n = s2.length(), sum = 0;
        int[][] dp = new int[m + 1][n + 1];
        for (char ch: s1.toCharArray()) sum += ch;
        for (char ch: s2.toCharArray()) sum += ch;
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                char a = s1.charAt(i - 1), b = s2.charAt(j - 1);
                if (a == b) dp[i][j] = dp[i - 1][j - 1] + a + b;
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return sum - dp[m][n];
    }
}

1458. 两个子序列的最大点积 https://leetcode.cn/problems/max-dot-product-of-two-subsequences/

https://leetcode.cn/problems/max-dot-product-of-two-subsequences/

在这里插入图片描述
这道题目有两点需要注意,子序列要求:1.长度相同 2.非空


这道题目一开始写成了:

class Solution {
    public int maxDotProduct(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                int v = nums1[i - 1] * nums2[j - 1];
                dp[i][j] = Math.max(v + dp[i - 1][j - 1], dp[i][j]);
            }
        }
        return dp[m][n];
    }   
}

结果发现答案要求是非空的,也就是如果只有负数的话,那没办法,负数也得选进去。


正解如下:

class Solution {
    public int maxDotProduct(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 0; i <= m; ++i) Arrays.fill(dp[i], Integer.MIN_VALUE / 2);	// 因为负数也得选,就先都设成一个比较小的数字
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                dp[i][j] = nums1[i - 1] * nums2[j - 1]; // 至少也得选一个
                dp[i][j] = Math.max(Math.max(dp[i - 1][j], dp[i][j - 1]), dp[i][j]);
                dp[i][j] = Math.max(nums1[i - 1] * nums2[j - 1] + dp[i - 1][j - 1], dp[i][j]);
            }
        }
        return dp[m][n];
    }
}

这道题目做起来手感怪怪的。

97. 交错字符串 https://leetcode.cn/problems/interleaving-string/

https://leetcode.cn/problems/interleaving-string/
在这里插入图片描述
dp[i][j] 表示 s1 的前 i 个元素 和 s2 的前 j 个元素 能否组成 s3 的前 i + j 个元素。

class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        int m = s1.length(), n = s2.length(), t = s3.length();
        if (m + n != t) return false;
        boolean[][] dp = new boolean[m + 1][n + 1];     // 表示能组合成的序列长度
        dp[0][0] = true;
        for (int i = 0; i <= m; ++i) {
            for (int j = 0; j <= n; ++j) {
                if (i > 0 && s1.charAt(i - 1) == s3.charAt(i + j - 1)) dp[i][j] |= dp[i - 1][j];
                if (j > 0 && s2.charAt(j - 1) == s3.charAt(i + j - 1)) dp[i][j] |= dp[i][j - 1];
            }
        }
        return dp[m][n];
    }
}

注意这个时候 i 和 j 的 for 循环的起始点都是 0 而不是 1 了

关于 s3 的下标为什么是 i + j - 1。举个例子,当 i = 1, j = 1,此时对应的两个下标都是0,那么这两个合起来之后对应 s3 的下标是 i + j - 2 和 i + j - 1。

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

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

相关文章

破解 Linux 文件安放之谜:哪里才是绝佳文件归宿?

想象一下&#xff0c;你盯着 Linux 中一大堆晦涩难懂的目录名。你想知道应该把特定类型的文件放在哪里。于是把文件随意放进了 /usr/share&#xff0c;希望这样做是正确的。 几天后&#xff0c;你发现应该把它放在 /var/local。 我们都有过类似的经历。Linux 的目录结构可能非…

如何在 AlmaLinux 上安装 Cockpit

Cockpit 是一个管理平台&#xff0c;允许管理员使用远程管理器轻松管理和控制他们的 GUI 或 CLI Linux 服务器系统。浏览器。驾驶舱可通过 Web 浏览器访问&#xff0c;其仪表板可让您查看服务器的健康状况以及其他系统统计数据&#xff0c;例如网络使用情况、磁盘空间和利用率、…

FPGA-DFPGL22学习3-调试手段

文章目录 前言一、调试前瞻二、Fabric Inserter 使用步骤1、选择探针2、开启调试 总结 前言 和原子哥一起学习FPGA 开发环境&#xff1a;正点原子 ATK-DFPGL22G 开发板 参考书籍&#xff1a; 《ATK-DFPGL22G之FPGA开发指南_V1.1.pdf》 个人学习笔记&#xff0c;欢迎讨论 一…

JVM调优相关

1.jvm中的一些工具 1.1 jps jps 用于查看java进程运行情况&#xff0c;输出JVM中运行的进程状态信息 命令行参数如下&#xff1a; -m 输出传入main方法的参数 -l 输出main类或Jar的全限名 -v 输出传入JVM的参数 如上&#xff0c;bootstrap 就是tomcat进程&#xff0c;调用…

C# ref / out 用法

目录 一、简介 二、ref 关键字 案例 注意点1 注意点2 三、out 关键字 案例 注意点1 注意点2 四、ref 和 out 关键字的相同点 五、ref 和 out 关键字的不同点 结束 一、简介 在C#中&#xff0c;ref和out关键字用于参数传递的方式。它们允许在方法内部对参数进行修改…

【无标题】宋词节选与中英对照

(https://img-blog.csdnimg.cn/03a0e9fdc924401fa7ab82d42a5b8dcc.jpg)

【剑指offer刷题记录 java版】链表双指针

本系列文章记录labuladong的算法小抄中剑指offer题目 【剑指offer刷题记录 java版】链表双指针 剑指 Offer II 025. 链表中的两数相加剑指 Offer 25. 合并两个排序的链表剑指 Offer 52. 两个链表的第⼀个公共节点剑指 Offer II 021. 删除链表的倒数第 n 个结点剑指 Offer II 02…

qt event事件处理

qt事件处理 qt事件处理比较恶心&#xff0c;各个事件都是独立的。如果同一时间出现多个事件&#xff0c;某些事件在qt中接收不到。 可以参考qtbase源码事件处理部分&#xff0c;所有的事件都在switch…case中处理&#xff0c;所以一次循环只会处理一个事件。 主窗口中可以通过…

【Rust 基础篇】Rust引用详解

文章目录 引言一、什么是引用&#xff1f;二、不可变引用三、可变引用四、引用的规则五、引用的使用建议六、示例代码总结 引言 在Rust中&#xff0c;引用是一种轻量级的指向数据的方式&#xff0c;它允许我们在不获取所有权的情况下访问和操作数据。引用是Rust中处理借用操作…

PostgreSql 逻辑结构

Database Cluser: 数据库集簇&#xff0c;一套服务器上安装部署完成的一套PostgreSql。在其中可创建数据库&#xff08;Database&#xff09;、用户&#xff08;User&#xff09;。User: 数据库用户&#xff0c;用来连接访问数据库&#xff0c;可通过权限管理&#xff0c;控制其…

360手机刷机 360手机Magisk面具安装与使用教程

360手机刷机 360手机Magisk面具安装与使用教程 参考&#xff1a;360手机-360刷机360刷机包twrp、root 360刷机包360手机刷机&#xff1a;360rom.github.io 【前序】 360手机通过Twrp&#xff0c;即可刷写Magisk文件&#xff1b;刷写成功后&#xff0c;即可获得root权限&#…

idm下载器怎么样好用吗?最新版本有哪些优势

日常工作中下载资料、音/视频等文件是常见的操作&#xff0c;如今市面上的软件非常多&#xff0c;根据我个人的使用经验idm非常不错。idm下载软件怎么样&#xff1f;idm下载软件不仅可下载的文件类型多&#xff0c;而且idm下载文件的速度非常快&#xff0c;同样下载文件的方法也…

电子证件照怎么弄?学会这几招在家也能做证件照

在很多情况下&#xff0c;人们需要制作证件照来证明自己的身份。例如&#xff0c;如果你想办理身份证、护照、驾驶证等证件&#xff0c;或者报考各类考试或申请学校、公司等机构&#xff0c;或者办理银行卡、社保卡等业务&#xff0c;或者申请签证或出入境手续&#xff0c;或者…

基于Java+Swing+Mysql商品信息管理系统

基于JavaSwingMysql商品信息管理系统 一、系统介绍二、功能展示1.主页2.新增商品信息3.查询商品信息 三、数据库四、其他系统实现五、获取源码 一、系统介绍 该系统实现了查看商品列表、新增商品信息、查询商品信息 运行环境&#xff1a;eclipse、idea、jdk1.8 二、功能展示…

IOC-DI(分层解耦)

问题-引出 可以发现我们之前的代码但是写在我们的controller程序中 这里因为比较简单 但是如果我们开发一个比较复杂的功能的话-会出现大量操作数据的代码 导致代码的复用性较差 且难以维护 分层解耦 三层架构 按照上面的对应代码不同功能 来分为下面这三个架构 对应的contr…

Elasticsearch:跨集群复制应用场景及实操 - Cross Cluster Replication

通过跨集群复制&#xff08;Cross Cluster Replication - CCR&#xff09;&#xff0c;你可以跨集群将索引复制并实现&#xff1a; 在数据中心中断时继续处理搜索请求防止搜索量影响索引吞吐量通过在距用户较近的地理位置处理搜索请求来减少搜索延迟 跨集群复制采用主动 - 被…

电商神器!教你如何利用数据分析打造销售奇迹!

能解决80%通用需求&#xff0c;提供销售、财务、广告、库存等电商数据分析主题的奥威BI电商数据分析方案一直都是比较神秘的存在。有说它风险低的&#xff0c;也有说它性价比高、效率高、可塑性高&#xff08;支持个性化开发&#xff09;&#xff0c;但说到底&#xff0c;这份B…

企业机房如何管理电池?分享一个实操方法

在现代的信息化社会中&#xff0c;机房扮演着至关重要的角色&#xff0c;是许多组织和企业的核心基础设施。而机房的稳定供电则离不开可靠的电池系统作为备用电源。 电池的状态监控和管理一直是机房管理者面临的挑战。为了确保机房的可靠运行和及时发现电池问题&#xff0c;动环…

二、部署Git服务器-Windows环境部署Gitea

目录 1. 说明2. 环境准备3. 安装部署3.1 安装Git3.2 安装Gitea3.3 将 Gitea 注册为 Windows 服务&#xff08;可选&#xff09;3.4 启用 Gitea 内置的 SSH 服务器&#xff08;可选&#xff09;3.5 编辑 Windows 防火墙 Gitea是一个自托管的Git服务&#xff0c;类似于GitHub、Gi…

设计模式学习之策略模式和简单工厂模式的对比

设计模式系列往期文章 设计模式学习之策略模式设计模式学习之策略模式在前端的应用设计模式学习之简单工厂模式设计模式学习之工厂方法模式设计模式学习之抽象工厂模式 在这之前我们已经介绍过了策略模式和简单工厂模式&#xff0c;也清楚策略模式属于行为模式&#xff0c;简…