题目地址:
https://www.lintcode.com/problem/k-edit-distance/description
给定一个字符串数组
A
A
A,再给定一个字符串
t
t
t和一个整数
k
k
k,要求返回
A
A
A中所有与
t
t
t编辑距离小于等于
k
k
k的字符串。编辑距离的定义是,一个字符串
s
s
s和
t
t
t的编辑距离等于至少执行多少次下面操作可以使得
s
s
s变成
t
t
t:
1、将
s
s
s删一个字符;
2、将
s
s
s改一个字符;
3、将
s
s
s加一个字符。
思路是Trie + DFS + 动态规划。关于两个字符串怎么求编辑距离,可以参考https://blog.csdn.net/qq_46105170/article/details/104585674。直接给出动态规划的转移方程,设 f [ i ] [ j ] f[i][j] f[i][j]是 s [ 0 : i − 1 ] s[0:i-1] s[0:i−1]和 t [ 0 : j − 1 ] t[0:j-1] t[0:j−1]的编辑距离,则有: f [ i ] [ j ] = { f [ i − 1 ] [ j − 1 ] , s [ i − 1 ] = t [ j − 1 ] min { 1 + f [ i ] [ j − 1 ] , 1 + f [ i − 1 ] [ j ] , 1 + f [ i − 1 ] [ j − 1 ] } , s [ i − 1 ] ≠ t [ j − 1 ] f[i][j]=\begin{cases}f[i-1][j-1], s[i-1]=t[j-1]\\ \min\{1+f[i][j-1],1+f[i-1][j],1+f[i-1][j-1]\}, s[i-1]\ne t[j-1] \end{cases} f[i][j]={f[i−1][j−1],s[i−1]=t[j−1]min{1+f[i][j−1],1+f[i−1][j],1+f[i−1][j−1]},s[i−1]=t[j−1]如果将 A A A中每个字符串都与 t t t算一下编辑距离就太慢了,我们发现,其实如果 A A A中有些字符串前缀一样,这个前缀与 t t t的编辑距离就被算了很多次。我们可以考虑用Trie + DFS的方法做。先把 A A A的所有字符串加入Trie,然后在Trie上做DFS,每遍历到一个节点,就计算其表示的前缀与 t t t的编辑距离,同时如果刚好这个节点对应一个 A A A中的字符串并且编辑距离恰好也是小于等于 k k k的话,就加入答案。代码如下:
import java.util.ArrayList;
import java.util.List;
public class Solution {
class Node {
Node[] next;
boolean isWord;
String word;
public Node() {
next = new Node[26];
}
}
private Node root;
private void insert(Node root, String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char ch = word.charAt(i);
if (cur.next[ch - 'a'] == null) {
cur.next[ch - 'a'] = new Node();
}
cur = cur.next[ch - 'a'];
}
cur.isWord = true;
cur.word = word;
}
/**
* @param words: a set of stirngs
* @param target: a target string
* @param k: An integer
* @return: output all the strings that meet the requirements
*/
public List<String> kDistance(String[] words, String target, int k) {
// write your code here
List<String> res = new ArrayList<>();
root = new Node();
for (int i = 0; i < words.length; i++) {
insert(root, words[i]);
}
int len = target.length();
// 一开始要算一下空串与target的编辑距离。这里dp[i]指的是空串与target长i的前缀的编辑距离
int[] dp = new int[len + 1];
for (int i = 0; i <= len; i++) {
dp[i] = i;
}
dfs(root, dp, res, target, k);
return res;
}
// dp[i]存的是cur代表的单词与t[0: i - 1]的编辑距离
private void dfs(Node cur, int[] dp, List<String> res, String target, int k) {
int n = target.length();
// 如果cur恰好是单词,并且编辑距离小于等于k,那就加入答案
if (cur.isWord && dp[n] <= k) {
res.add(cur.word);
}
// 这里需要新开一个dp数组,名为nextDp,如果直接对dp进行修改的话,回溯的时候是无法恢复现场的
int[] nextDp = new int[n + 1];
for (int i = 0; i < 26; i++) {
if (cur.next[i] == null) {
continue;
}
// 当前字符串肯定比上层递归传过来的字符串长度多1
nextDp[0] = dp[0] + 1;
for (int j = 1; j <= n; j++) {
// 这里就是编辑距离的转移方程了
if (i == target.charAt(j - 1) - 'a') {
nextDp[j] = dp[j - 1];
} else {
nextDp[j] = Math.min(Math.min(1 + nextDp[j - 1], 1 + dp[j]), 1 + dp[j - 1]);
}
}
dfs(cur.next[i], nextDp, res, target, k);
}
}
}
时间复杂度和Trie的具体形态有关,但这样的做法可以保证每个前缀与 t t t的编辑距离只会被算一次。空间复杂度也与Trie的具体形态有关。