题目描述
题目地址:https://leetcode-cn.com/problems/delete-operation-for-two-strings/submissions/
原题说的是要找到两个字符串中,每次删去一个任意位置的字符,最少删去多少次能让两个字符串相等。
思路与实现
很直观能看到是要找最长子串。不过要注意的是,这里的字串指的是有序可不连续的字符序列。
举个例子:park与spake的最长“字串”指的是pak。
最初的想法没有想到这里的字串包括不连续的,于是上面的用例便没过,之后思路就偏了,集中到怎么删去字符进行比较。想法是为每个原字符串构建一棵树,树的第i层表示删去第i次,之后遍历这两颗树找最长的字串。穷举的思路当然时间爆炸,在长度为8左右的用例就tle了……
后来翻了题解,这道题的正确思路应该是dp,方程:
$result[i][j] = \left\{\begin{array}{lr} result[i-1][j-1] + 1 & & {one[i]=another[i]} \\ max(result[i][j-1],res[i-1][j]) & & {one[i] \neq another[j]} \end{array} \right.$
方程中$one$,$another$表示输入的两个字符串,$result[i][j]$表示$one$前$i$个字符中与$another$前$j$个字符中最长“字串”(可不连续有序子序列)的长度。
对任意时刻令$context = (i, j)$,此时有两种情况,第$i,j$这两个字符相等或不等。相等时结果应该是上一个$context$计算后的结果加1,即$result[i - 1][j - 1]$。
不相等时,此时不会出现新的子串因为当前不相等,所以新的一次计算不会对子串的个数造成影响,因此我们选两边中上一次结果比较大的一边作为这次结果。
直观dp实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class Solution {
private int result(char[] oneCharArray, int i, char[] anotherCharArray, int j) {
if (i < 0 || j < 0) { return 0; }
if (oneCharArray[i] == anotherCharArray[j]) { return result(oneCharArray, i - 1, anotherCharArray, j - 1) + 1; } else { return Math.max(result(oneCharArray, i - 1, anotherCharArray, j), result(oneCharArray, i, anotherCharArray, j - 1)); }
}
public int minDistance(String word1, String word2) {
int maxSubSeqLength = result(word1.toCharArray(), word1.length() - 1, word2.toCharArray(), word2.length() - 1);
return word1.length() + word2.length() - 2 * maxSubSeqLength; }
}
|
过了26个用例左右超时了(dp日常tle…),使用常规数组优化,具体思路可以结合注释看。实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class Solution {
private int result(int[][] cache, char[] oneCharArray, int i, char[] anotherCharArray, int j) {
if (i < 0 || j < 0) { return 0; }
if (oneCharArray[i] == anotherCharArray[j]) { if (cache[i][j] != -1) { cache[i + 1][j + 1] = cache[i][j] + 1; return cache[i + 1][j + 1]; }
return result(cache, oneCharArray, i - 1, anotherCharArray, j - 1) + 1; } else {
int oneSide = cache[i][j + 1] != -1 ? cache[i][j + 1] : result(cache, oneCharArray, i - 1, anotherCharArray, j); int anotherSide = cache[i + 1][j] != -1 ? cache[i + 1][j] : result(cache, oneCharArray, i, anotherCharArray, j - 1); cache[i + 1][j + 1] = Math.max(oneSide, anotherSide); return cache[i + 1][j + 1]; }
}
public int minDistance(String word1, String word2) {
int[][] cache = new int[word1.length() + 1][word2.length() + 1];
for (int i = 1; i < cache.length; i++) { for (int j = 1; j < cache[0].length; j++) { cache[i][j] = -1; } }
int maxSubSeqLength = result(cache, word1.toCharArray(), word1.length() - 1, word2.toCharArray(), word2.length() - 1);
return word1.length() + word2.length() - 2 * maxSubSeqLength; }
}
|
最后一个是题解中取消递归的实现,时间进一步优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public int minDistance(String word1, String word2) {
char[] word1cc = word1.toCharArray(); char[] word2cc = word2.toCharArray();
int[][] publicSeqLength = new int[word1cc.length + 1][word2cc.length + 1];
for (int i = 1; i <= word1cc.length; i++) { for (int j = 1; j <= word2cc.length; j++) { if (word1cc[i - 1] == word2cc[j - 1]) { publicSeqLength[i][j] = publicSeqLength[i - 1][j - 1] + 1; } else { publicSeqLength[i][j] = Math.max(publicSeqLength[i - 1][j], publicSeqLength[i][j - 1]); } } }
return word1cc.length - publicSeqLength[word1cc.length][word2cc.length] + word2cc.length - publicSeqLength[word1cc.length][word2cc.length]; } }
|
小结
首先要看出是dp,然后找方程,注意要把每次迭代都需要什么信息考虑清楚,方程列出来之后就都是套路比较好办,重点还是看出是dp而且列对方程。
这几次的练习主要在做dp,但是这道题还是思路偏了,总结起来就是不熟练,不熟悉套路,总之多多练习,目前接触的题还是太少太少了。