个人博客:The Blog Of WaiterXiaoYY 欢迎来互相交流学习。
被动态规划折磨了这么久,今天总算是可以说有点手感了,
编辑距离也算是动态规划里面比较经典的,
当拿到题目的时候,也是很懵的,不愧是困难程度,
但如果按照动态规划的套路来分析问题,
或许能清晰一点,
动态规划的套路是什么?
分清状态和选择。
我们先来看一下题目:
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
给定两个单词,比如word1 = "horse",word2= "ros"
,
如果将 horse 转换成 ros ,怎么增/删/换的步骤是最少的,
我们知道这道题最少的步骤是 3,
具体是这样做的:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
那怎么样才能计算出最少的步骤?
穷举!
穷举什么?
对每一处都进行三次选择,
再取能令总的操作数最小的操作。
很抽象对不对。
做这个笔记时,曾一度中止,主要是在脑子里面无法形成生动形象的言语在字面上表现出来,
但如果理解了,现实中三言两语就可以说的清楚。
先看回我们这道题,
对比两个字符串,我们一般采用双指针,
上面操作的第一步是将 h 替换成 r,这其实是我们判断完后才知道的操作,
我们应该怎么判断?
我们需要两个指针,这两个指针分别指向两个字符串尾部,如图:
此时进行判断,
如果此处字符对应相等,那么不做任何操作,p,q都往前移动,操作数等于移动后对应的操作数,
这里不理解?先暂时跳过加粗的部分,你现在只需要知道如果相同,就一起前移。
但实际上这里的两个字符是不同的,
那就得进行选择,到底是删掉e,还是将e替换成s,还是在后面补上s呢?
小孩子才做选择,我全部都试一遍,
如果是删掉e,将变成这样:
p将迁移,q保持不动,
好,这是删掉的情况,如果是替换的情况呢?
替换之后就相等了,那么p,q都将往前迁移,如图:
如果是增加呢?
那就是在 e 的后面加上 s,然后p要保持不动,q所指已经有匹配了,所以 q 要向前移动,如图:
好,到这里,以上每种操作,操作数都是 1,执行完之后,我们的操作数 + 1,
咦?为什么是 1 不是 3 呢?
因为我们只需要上面的其中一种,虽然我们进行了三次操作,但是只有一种操作能使总的操作数最少,是谁呢?
暂时还不清楚,得看谁?
得看执行后的的位置上进行这样三次操作,一直到其中一个字符串被遍历完,
所以这就转换成为我们熟悉的 最优解子问题,
要看我这个操作是不是能使总的操作数达到最小,就得看我的子问题能不能使操作数达到最小,
这就是我们的状态和选择,
回到我刚刚说的先不理会的加粗部分,如果此时两个字符相等,就一起迁移,
此时的问题就转成为子问题的求解,子问题最小,则其最小。
所以状态和选择我们都搞清楚了,只需要再搞清楚结束条件和初始值,就可以列举出我们的状态转移方程了。
- 结束条件是什么?
当 p 或者 q指向字符串的前面时候,就代表结束了,以下展示的是同时到达最前面,也就是这个问题最优的情况,
如果是这样呢?
q走完了,但是p还没有走完,那就用删除操作把p前面的全部删掉,
如果是 p 走完了,但是 q 没走完,那么就用增加操作将 q 前面的所有加到 p 所指的位置,
- 那初始值是什么?
当然就是一方字符串为空,操作数就是另一字符串的长度。
到了这里,应该我们的状态转移方程就可以写出来了:
dp[i][j] = min(dp[i - 1][j - 1] + 1, dp[i - 1][j] + 1, dp[i][j - 1] + 1)
// i是指向word1的指针,j是指向word2的指针
//dp[i][j]表示当前位置的操作所能达到的最小操作数
//dp[i - 1][j - 1] + 1 表示替换,替换后的子问题加上1就是当前的最小操作数
//dp[i - 1][j] + 1 表示删除,删除后的子问题加1就是当前的最小操作数
//dp[i][j - 1] + 1 表示增加,增加后的子问题加1就是当前的最小操作数
代码
class Solution {
public int minDistance(String word1, String word2) {
//将字符串转为字符数组
char []s1 = word1.toCharArray();
char []s2 = word2.toCharArray();
//创建一个二维数组
int [][]dp = new int[s1.length + 1][s2.length + 1];
//初始值条件
for(int i = 1; i <= s1.length; i++)
dp[i][0] = i;
for(int j = 1; j <= s2.length; j++)
dp[0][j] = j;
int temp;
//开始遍历两个字符串
for(int i = 1; i <= s1.length; i++) {
for(int j = 1; j <= s2.length; j++) {
//如果当前位置对应相等,则当前的最小操作数等于子问题的操作数
if(s1[i - 1] == s2[j - 1])
dp[i][j] = dp[i - 1][j - 1];
else {
//判断三种操作的最小操作数
temp = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
dp[i][j] = Math.min(dp[i - 1][j - 1] + 1, res);
}
}
}
return dp[s1.length][s2.length];
}
}
呼~
整理于2020.3.26