动态规划
先来看dp数组的构成,把word1放在数组的左边,用行来表示每个字符,把word2放在数组的上边,用列来表示每个字符。word1和word2前面都有一个"",因为dp[0][0]表示”空字符转变到空字符“需要的次数。
再来看一下dp[i][j]的含义,它表示word1的前i个字符组成的子串转换成word2的前j个字符的组成子串,需要的最少次数。
将horse转变成ros,需要的操作无非是插入,删除,替换,不变动,用dp数组来表示就是:
为什么可以这样表示呢?下面一一解释
插入操作(dp[i][j] = dp[i][j - 1] + 1):
看第一行
""转变成ros的前1个字符r,是不是需要插入r,翻译成代码等于dp[0][1] = dp[0][0] + 1,+1表示当前插入这个操作""转变成ros的前2个字符ro,是不是先插入r再插入o,翻译成代码等于dp[0][2] = dp[0][1] + 1""转变成ros的前3个字符ros,是不是先插入r再插入o再插入s,翻译成代码等于dp[0][3] = dp[0][2] + 1
可以看出插入操作是往右移动的,dp[i][j] = dp[i][j - 1] + 1成立
删除操作(dp[i][j] = dp[i - 1][j] + 1):
看第一列
horse的前1个字符h转变成"",是不是需要删除h,翻译成代码等于dp[1][0] = dp[0][0] + 1horse的前2个字符ho转变成"",是不是需要先删除h再删除o,翻译成代码等于dp[2][0] = dp[1][0] + 1horse的前3个字符hor转变成"",是不是需要先删除h再删除o再删除r,翻译成代码等于dp[3][0] = dp[2][0] + 1
可以看出删除操作是往下移动的,dp[i][j] = dp[i - 1][j] + 1成立
替换操作(dp[i][j] = dp[i - 1][j - 1] + 1)和不变动(dp[i - 1][j - 1]):
观察dp[1][1],意思是将h替换为r,其实就是操作数原地+ 1,'原地'也就是'不操作,不变动'的意思,所以替换操作== dp[i][j] = dp[i - 1][j - 1] + 1,不变动 == dp[i - 1][j - 1]
算法思路:
- 将
String转换成字符数组,比使用word1.charAt(i)效率要高 - 如果
i指向的字符==j指向的字符,则选择不变动 - 如果不相等,则选择
插入,删除,替换中操作数最小的一个
完整的转变路径如下:
h变成r,o不变,删除r,s不变,删除e
public int minDistance(String word1, String word2) {
int rows = word1.length(), columns = word2.length();
int[][] dp = new int[rows + 1][columns + 1];
char[] word1Array = word1.toCharArray();
char[] word2Array = word2.toCharArray();
for (int i = 1; i <= rows; i++) {
dp[i][0] = i;
}
for (int j = 1; j <= columns; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= rows; i++) {
for (int j = 1; j <= columns; j++) {
if (word1Array[i - 1] == word2Array[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
}
}
}
return dp[rows][columns];
}