“编辑距离”系列总结,一文读懂(Java实现)

目录

一、判断子序列 ——>删除元素

1.1、dp定义

1.2、递推公式

1.3、初始化

1.4、遍历顺序

1.5、解题代码

二、不同的子序列 ——>删除元素

2.1、dp定义

2.2、递推公式

2.3、初始化

2.4、遍历顺序

2.5、解题代码

三、两个字符串的删除操作 ——>删除元素

3.1、dp定义

3.2、递推公式

3.3、初始化

3.4、遍历顺序

3.5、解题代码

四、编辑距离 ——>删除、增加、替换元素

4.1、dp定义

4.2、递推公式

4.3、初始化

4.4、遍历顺序

4.5、解题方程

小结


一、判断子序列 ——>删除元素

题目描述:

题目来源:392. 判断子序列

1.1、dp定义

 分析:

        判断s是否为t的子序列,实际上就是在求s和t的最长公共子序列的长度是否和s的长度相等,这个问题也是“编辑距离”中的一个入门级别的题目。

如果对最长公共子序列不太理解的,可以看看这篇:

http://t.csdn.cn/DAaaK

dp定义:

dp[i][j]表示字符串s以i - 1为结尾,字符串t以j - 1为结尾的最长公共子序列的长度。

建议:对于两个字符串要比较相同字符的情况,都最好定义成以i - 1,j - 1结尾~

1.2、递推公式

分析:

dp[i][j]就表示最长公共子序列的长度,我们该怎么推出他呢?

1.当s[i] - 1 == t[i - 1]时, 说明当前这对字符相等,如下:

那么dp[i][j]就等于s和t相同的这个字符前面这一段最长公共子序列 + 1得到,+1就表示s和t的当前对比的字符相等,所以在原来的基础上dp[i - 1][j - 1]进行+1操作,如下:

2.当s[i] != t[i]时,说明当前这对字符不相等,那么就不进行+1操作,那么当前状态dp[i][j]就可以由dp[i][j - 1]来进行表示,相当于删除了t的一个字符,让s的第i - 1和t的第j - 2个元素比较,而dp[i][j - 1]这个状态,我们在上一次的递推已经的出来了,所以可以直接拿来用;

递推公式:

if(s.charAt(i - 1) == t.charAt(j - 1)) { 

        dp[i][j] = dp[i - 1][j - 1] + 1;

} else {

        dp[i][j] = dp[i][j - 1];

}

1.3、初始化

分析:

之前我们将dp数组定义成i - 1和j - 1,为什么不是i,j呢?原因就在初始化这里!

        若定义成i,j,假设我们定义成以i为结尾,初始化的时候nums[i][0]和nums[0][j]该怎么初始化?dp[i][0]就是“nums1以nums1[i]为结尾,nums2以nums2[0]为结尾的最长子数组”,那么我们就需要揪住nums2[0]这个元素去遍历nums1这个数组,去统计相同的元素,同理,nums[0][j]也是如此!

        但如果我们定义成i - 1, j - 1,nums[i][0]这里的0 - 1就是-1,相当于空串,空串的最长公共子序列自然是0,所以直接初始化成0即可;dp数组中其他元素初始化成什么都可以,因为在递推的过程中都会被覆盖。

1.4、遍历顺序

        由递推公式可以知道,推出dp[i][j]都是需要前一个元素,所以只需要从上到下,从左往右遍历即可~

1.5、解题代码

class Solution {
    public boolean isSubsequence(String s, String t) {
        int len1 = s.length();
        int len2 = t.length();
        int[][] dp = new int[len1 + 1][len2 + 1];
        for(int i = 1; i <= len1; i++) {
            for(int j = 1; j <= len2; j++) {
                if(s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = dp[i][j - 1];
                }
            }
        }
        return dp[len1][len2] == s.length();
    }
}

二、不同的子序列 ——>删除元素

题目描述:

题目来源:115. 不同的子序列

2.1、dp定义

分析:

按照之前的套路, 对于两个字符串要比较相同字符的情况,定义成以i - 1,j - 1结尾即可。

定义:

dp[i][j]表示以字符串s的第i - 1个字符为结尾,以字符串t的第j - 1个字符为结尾,s的子序列中t的个数。

2.2、递推公式

分析:

1.当s[i - 1] == t[j - 1]时,就不用考虑当前i-1,j-1为结尾表示的字符了,也就是考虑i-2,j-2为结尾的个数dp[i - 1][j - 1],因为这个状态之前已经推出,直接拿来用即可,如下:

dp[i][j] = dp[i - 1][j - 1]就完了吗?实际上我们这里写的s[i - 1]表示是否要考虑这个元素,以上是考虑这个元素,那么不考虑又是怎么回事呢?如下:

为什么不考虑dp[i][j - 1]呢?因为本题求的是从s的子序列中有多少个t!

 2.当s[i - 1] != t[j - 1]时,就删掉s一个元素,考虑s的以s[i - 2]为结尾的序列即可。

递推公式:

if(s.charAt(i - 1) == t.charAt(j - 1)) {

        dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];

} else {

        dp[i][j] = dp[i - 1][j];

}

2.3、初始化

分析:

从递推公式中可以看出,一直往前递推,找到根源就是需要知道dp[i][0]和dp[0][j]。

dp[i][0]表示s以i - 1为结尾,t以0 - 1为结尾,s的子序列中t的个数。

那么t就相当于是空串,s的子序列中有几个空串呢?当把s中所有的字符都删除掉,就是一个空串了,因此dp[i][0] = 1

dp[0][j]表示s以0 - 1为结尾,t以j - 1为结尾,s的子序列中t的个数。

这里无论s本身就是一个空串了,就更别谈子序列了,也就是说s无论怎么删,t都不可能等于s,除非t也是空串,因此dp[j][0] = 0,dp[0][0] = 1;

初始化:

 dp[i][0] = 1;

dp[j][0] = 0;

dp[0][0] = 1;

2.4、遍历顺序

由递推公式可以知道,只有从以下几个方面可以得到dp[i][j]:

因此遍历顺序因该是从上往下,从左往右。

2.5、解题代码

class Solution {
    public int numDistinct(String s, String t) {
        int len1 = s.length();
        int len2 = t.length();
        int[][] dp = new int[len1 + 1][len2 + 1];
        //初始化
        for(int i = 0; i <= len1; i++) {
            dp[i][0] = 1;
        }
        //递推
        for(int i = 1; i <= len1; i++) {
            for(int j = 1; j <= len2; j++) {
                if(s.charAt(i - 1) == t.charAt(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];
    }
}

三、两个字符串的删除操作 ——>删除元素

题目描述:

题目来源:583. 两个字符串的删除操作 

3.1、dp定义

分析:

        对于两个字符串要比较相同字符的情况,都最好定义成以i - 1,j - 1结尾(如果不明白,建议看看上面几道题的讲解,已经说的很清楚了),dp[i][j]就定义成最终问题的解即可。

dp定义:

dp[i][j]:字符串word1以i - 1为结尾,字符串word2以j - 1为结尾的,使两字符串相同的最小步数。

3.2、递推公式

分析:

1.当w1[i - 1] == w2[j - 1]时,也就是当前对比的两个字符相等时,说明不需要进行删除操作,就可以先忽略这对字符,用这一对字符前面的字符串(dp[i - 1][j - 1])表示使字符串相同的最小步数即可。

2.当w1[i - 1] != w2[j - 1]时,也就是当前对比的两个字符不相等时,说明需要进行删除操作,这时候就需要考虑到底是删w1[i - 1]合适(删除后,比较之前进行删除操作的步骤更少)还是删w2[j - 1]合适,那么这时候就需要看dp[i - 1][j]和dp[i][j - 1]谁更小,比较删除当前字符后,对比之前进行删除操作的步骤谁更少,最后再+1,表示删除当前字符这个步骤。

递推公式:

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;

}

3.3、初始化

分析:

        从递推公式中可以看出,要初始化 dp[i - 1][j - 1]、dp[i - 1][j]、dp[i][j - 1] 一直往前递推,找到根源就是需要知道dp[i][0]和dp[0][j]。

1.dp[i][0]表示w1以i - 1为结尾,w2以0 - 1为结尾,使w1和w2相同的最小步数。

也就是说w2是一个空串,通过删除w1的字符,使两个字符串相等,具体的,如下图:

2.dp[0][j]同理。

初始化:

for(int i = 0; i <= len1; i++) {

        dp[i][0] = i;

}

for(int j = 0; j <= len2; j++) {

        dp[0][j] = j;

}

3.4、遍历顺序

 由递推公式可以知道,只有从以下几个方面可以得到dp[i][j]:

因此遍历顺序因该是从上往下,从左往右。

3.5、解题代码

class Solution {
    public int minDistance(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        int[][] dp = new int[len1 + 1][len2 + 1];
        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(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[len1][len2];
    }
}

四、编辑距离 ——>删除、增加、替换元素

Ps:建议理解了上面讲解的三道题,再来看这道题,就不难掌握了~

题目描述:

题目来源:72. 编辑距离

4.1、dp定义

分析:

        对于两个字符串要比较相同字符的情况,都最好定义成以i - 1,j - 1结尾(如果不明白,建议看看上面几道题的讲解,已经说的很清楚了),dp[i][j]就定义成最终问题的解即可。

dp定义:

dp[i][j]:字符串w1以i - 1为结尾,字符串w1以j - 1为结尾,w1转化成w2的最少操作次数。

4.2、递推公式

分析:

1.当前对比的这对字符相同时,就不用进行任何的编辑操作(增加、删除、替换),也就是说可以忽略这对字符,直接用这对 字符 前面的 字符串的状态(dp[i - 1][j - 1])来表示当前状态dp[i][j];

2.当前对比的这对字符不相同时,想要让w1转化成w2,可以对字符进行增加、删除、替换的操作,最后从这些操作中选出一个操作次数最少的来表示dp[i][j],具体的:

1) 增加、删除元素

        先来谈谈删除元素,当前这对字符不相同,我们因该考虑的是删除w1[i]还是w2[j]这个字符合适,怎么才算合适?就要看忽略(这里的忽略就是删除操作)了w1[i](忽略后的状态为dp[i - 1][j],w1以i - 2为结尾,w2以j - 1为结尾的最小编辑距离)或w2[j](忽略后的状态为dp[i][j - 1])以后,谁的编辑距离要更小!最后再+1,这个+1就表示删除元素这一步的操作,最后就得到了dp[i][j]这个状态。

        那么增加元素呢?实际上,增加和删除元素的转移方程是一样的,因为站在w1的角度来删除自身的元素,站在w2的角度看就是在增加自己的元素,如下图:

2)替换元素

        替换元素就是在dp[i -1][j - 1]这个状态的基础上,通过替换,使下一对字符相等的一个操作!也就是说,替换就是为了当前这对元素相等,在dp[i - 1][j - 1]这个状态上通过一个+1(+1就是替换)的操作,使下一对字符变成相等

状态转移方程:

if(word1.charAt(i - 1) == word2.charAt(j - 1)) {
    dp[i][j] = dp[i - 1][j - 1];
} else {
    //增加删除
    int addDel = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1;
    //替换
    int replace = dp[i - 1][j - 1] + 1;
    dp[i][j] = Math.min(addDel, replace);
}

4.3、初始化

分析:

        从递推公式中可以看出,要初始化 dp[i - 1][j - 1]、dp[i - 1][j]、dp[i][j - 1] 一直往前递推,找到根源就是需要知道dp[i][0]和dp[0][j]。

1.dp[i][0]表示w1以i - 1为结尾,w2以0 - 1为结尾,使w1和w2相同的最小编辑距离

也就是说w2是一个空串,通过删除w1的字符或增加w2的字符,使两个字符串相等,具体的,如下图:

2.dp[0][j]同理。

初始化:

for(int i = 0; i <= len1; i++) {

        dp[i][0] = i;

}

for(int j = 0; j <= len2; j++) {

        dp[0][j] = j;

}

4.4、遍历顺序

由递推公式可以知道,只有从以下几个方面可以得到dp[i][j]:

因此遍历顺序因该是从上往下,从左往右。

4.5、解题方程

class Solution {
    public int minDistance(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        int[][] dp = new int[len1 + 1][len2 + 1];
        //初始化
        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(word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    //增加删除
                    int addDel = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1;
                    //替换
                    int replace = dp[i - 1][j - 1] + 1;
                    dp[i][j] = Math.min(addDel, replace);
                }
            }
        }
        return dp[len1][len2];
    }
}

小结

面试遇到,还不得装一装(doge)


  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值