当前位置: 首页 > news >正文

算法训练(leetcode)二刷第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离

刷题记录

  • *115. 不同的子序列
  • 583. 两个字符串的删除操作
    • 思路一:转求公共子序列
    • 思路二:编辑距离(统计删除次数)
  • 72. 编辑距离

*115. 不同的子序列

leetcode题目地址

编辑距离问题。

题目要求在s串中查找t串出现的次数。

dp数组含义:dp[i][j] 表示以s[i-1]结尾的子串A中出现以t[j-1]为结尾的子串B的个数

状态转移方程:
题目要求在s串中查找t串出现的次数,因此只考虑对s串进行编辑。

  • s[i-1] == t[j-1]时,dp[i][j] = dp[i-1][j-1] + dp[i-1][j],分别考察使用s[i-1]不使用s[i-1]
    • dp[i-1][j-1]:(使用s[i-1])以s[i-2]结尾的子串A中出现以t[j-2]为结尾的子串B的个数,也就是不考虑当前s串和t串正在查看的当前字符,查看两个子串前一位的匹配情况(就是考虑将当前字符加入子串)
    • dp[i-1][j-1]:(不使用s[i-1])以s[i-2]结尾的子串A中出现以t[j-1]为结尾的子串B的个数,也就是不考虑当前s串正在查看的当前字符,即删除s[i-1]

时间复杂度: O ( n ∗ m ) O(n*m) O(nm)
空间复杂度: O ( n ∗ m ) O(n*m) O(nm)

// java
class Solution {
    public int numDistinct(String s, String t) {
        int len1 = s.length(), len2 = t.length();
        char[] sArray = s.toCharArray(), tArray = t.toCharArray();

        int[][] dp = new int[len1+1][len2+1];
        for(int i=0; i<=len1; i++) dp[i][0] = 1;
        

        int result = 0;
        
        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                if(sArray[i-1] == tArray[j-1]){
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }  
        return dp[len1][len2] % (int)(Math.pow(10, 9)+7);
    }
}

583. 两个字符串的删除操作

leetcode题目地址

思路一:转求公共子序列

同样是编辑距离问题。

本题仅考虑删除问题,因此较简单。

先将问题转化:

  • 原问题:每步可以删除任意一个字符串中的一个字符。
  • 转化:先求两个字符串的公共子序列长度,然后两个子序列中各自多出来的其余字符就是要删除的。
    统计公共子序列长度公共子序列长度是前面做过的(题解),因此直接把代码搬过来既可。

题目要求返回两个字符串总共删除的字符个数,设公共子序列长度为x,则A、B串中各自有长为x的子串,因此要删除的字符总数 = A串长度 + B串长度 - x * 2

时间复杂度: O ( n ∗ m ) O(n * m) O(nm)
空间复杂度: O ( n ∗ m ) O(n * m) O(nm)

// java
class Solution {
    public int minDistance(String word1, String word2) {
        char[] arr1 = word1.toCharArray(), arr2 = word2.toCharArray();

        int len1 = arr1.length, len2 = arr2.length;
        
        // dp[i][j]:以s[i-1]和t[j-1]的两个子串公共子序列长度
        int[][] dp = new int[len1+1][len2+1];

        // 状态转移方程:
        // s[i-1]==t[j-1]: dp[i][j] = dp[i-1][j-1] + 1;
        // s[i-1]!=t[j-1]: dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);

        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                if(arr1[i-1] == arr2[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  len1 + len2 - dp[len1][len2]*2;
    }
}

思路二:编辑距离(统计删除次数)

dp数组含义:dp[i][j]表示以word1[i-1]和word2[j-1]结尾的相同子串需要删除的最小步数
状态转移方程:

  • word1[i-1] == word2[j-1]: dp[i][j] = dp[i-1][j-1]
  • word1[i-1] != word2[j-1]:
    • 删除word1[i-1]:dp[i-1][j] + 1
    • 删除word2[j-1]:dp[i][j-1] + 1
    • 删除word1[i-1]和word2[j-1]:dp[i-1][j-1] + 2
      综上,dp[i][j]更新为三者取最小,即:
      dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 2)

PS: 这里dp[i-1][j-1] + 2其实相当于dp[i][j-1] + 1或dp[i-1][j],用dp[i][j-1] + 1举例:
dp[i][j-1] + 1是考虑word1[i-1]和不考虑word2[j-1],在这个基础上再不考虑word1[i-1]就是dp[i-1][j-1] + 1 + 1也就是dp[i-1][j-1] + 2,因此在第二部两字符不相等时,更新dp[i][j]仅需要比较两个串各自不考虑当前字符即可,即:dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1)

// java
class Solution {
    public int minDistance(String word1, String word2) {
        char[] arr1 = word1.toCharArray(), arr2 = word2.toCharArray();

        int len1 = arr1.length, len2 = arr2.length;
        int[][] dp = new int[len1+1][len2+1];

        // dp数组含义:dp[i][j]表示以s[i-1]和t[j-1]结尾的相同子串需要删除的最小步数
        // 状态转移方程
        // s[i-1] == t[j-1]: dp[i][j] = dp[i-1][j-1]
        // s[i-1] != t[j-1]: 
        // dp[i-1][j] + 1
        // dp[i][j-1] + 1
        // dp[i-1][j-1] + 2
        // dp[i][j] = Math.min(Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + 2)

        // 初始化
        // dp[0][j] = j dp[i][0] = i
        for(int i=0; i<=len1; i++) dp[i][0] = i;
        for(int j=0; j<=len2; j++) dp[0][j] = j;

        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                if(arr1[i-1] == arr2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    dp[i][j] = Math.min(Math.min(dp[i-1][j]+1, dp[i][j-1]+1), dp[i-1][j-1]+2);
                }
            }
        }
        return dp[len1][len2];
        
    }
}

72. 编辑距离

leetcode题目地址

dp数组含义:dp[i][j]表示以word1[i-1]和word2[j-1]结尾的相同串所需最少操作数

状态转移方程:

  • word1[i-1]==word2[j-1]: dp[i][j] = dp[i-1][j-1]
  • word1[i-1]!=word2[j-1]:
    • 删除word1[i-1](也就是word1[i-2]和word2[j-1]匹配,word1[i-1]多余): dp[i][j] = dp[i-1][j] + 1
    • 向word1插入word2[j-1](也就是word1[i-1]和word2[j-2]匹配,但当前word2[j-1]缺少匹配): dp[i][j] = dp[i][j-1] + 1
    • 替换word1[i-1]为word2[j-1](也就是word1[i-2]和word2[j-2]匹配,但word1[i-1]和word2[j-1]不匹配):dp[i][j] = dp[i-1][j-1] + 1
      综上,三者取最小,即:
      dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1)

初始化:dp[i][0] = i, dp[0][j] = j

时间复杂度: O ( n ∗ m ) O(n*m) O(nm)
空间复杂度: O ( n ∗ m ) O(n*m) O(nm)

// java

class Solution {
    public int minDistance(String word1, String word2) {
        char[] arr1 = word1.toCharArray(), arr2 = word2.toCharArray();

        int len1 = arr1.length, len2 = arr2.length;
        int[][] dp = new int[len1+1][len2+1];
       
        // dp数组含义:dp[i][j]表示以word1[i-1]和word2[j-1]结尾的相同串所需最少操作数

        // 状态转移方程:
        // word1[i-1]==word2[j-1]: dp[i][j] = dp[i-1][j-1]
        // word1[i-1]!=word2[j-1]: 
        // 删除word1[i-1]:  dp[i][j] = dp[i-1][j] + 1
        // 向word1插入word2[j-1]: dp[i][j] = dp[i][j-1] + 1
        // 替换word1[i-1]为word2[j-1]:dp[i][j] = dp[i-1][j-1] + 1
        // 三者取最小

        // 初始化:dp[i][0] = i dp[0][j] = j
        for(int i=0; i<=len1; i++) dp[i][0] = i;
        for(int j=0; j<=len2; j++) dp[0][j] = j;

        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                if(arr1[i-1] == arr2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                } else{
                    dp[i][j] = Math.min(Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + 1);
                }
            }
        }
        return dp[len1][len2];
    }
}

相关文章:

  • 最短路问题
  • linux上配置免密登录
  • 【每日五题系列】前端面试高频题目
  • VSCode 配置优化指南:打造极致高效的前端开发环境
  • 如何高效使用 Mybatis-Plus 的批量操作
  • java中小型公司面试预习资料(一):基础篇
  • python文本处理pdfminer库安装与使用
  • ZYNQ-PL学习实践(五)IP核之FIFO
  • Oracle SQL优化实战要点解析(11)——索引、相关子查询及NL操作(1)
  • Gartner发布2025年网络安全六大预测
  • PROFINET转PROFIBUS从案例剖析网关模块的协议转换功能
  • 蓝桥杯备赛:每日一题
  • 数据库两个表数据同步的核心方案与深度实践
  • Linux 下的 Docker 安装与使用
  • 第十五届蓝桥杯----B组cpp----真题解析(小白版本)
  • 给没有登录认证的web应用添加登录认证(openresty lua实现,代码已完善)
  • 迷你世界脚本自定义UI接口:Customui
  • NO.28十六届蓝桥杯备战|string|insert|find|substr|关系运算|stoi|stol|stod|stof|to_string(C++)
  • 蓝桥杯 封闭图形个数
  • NanoMQ ds笔记250306
  • 企业做网站的公司/武汉排名seo公司
  • 公司网站里面页面链接怎么做/电商培训心得
  • 做视频包的网站有哪些/百度账号客服人工电话
  • 电商网站设计周志/湖南网络推广排名
  • 坪地网站建设/广州网络广告推广公司
  • 网站群系统/seo站外推广有哪些