【发布时间】:2011-01-13 09:57:42
【问题描述】:
我遇到了edit-distance 问题的这种变体:
设计一种将源词转换为目标词的算法。例如:从头到尾,每一步只能替换一个字符,单词必须有效。你会得到一本字典。
这显然是edit distance 问题的变体,但在编辑距离上,我不关心这个词是否有效。那么如何将这个要求添加到编辑距离。
【问题讨论】:
标签: algorithm string transform
我遇到了edit-distance 问题的这种变体:
设计一种将源词转换为目标词的算法。例如:从头到尾,每一步只能替换一个字符,单词必须有效。你会得到一本字典。
这显然是edit distance 问题的变体,但在编辑距离上,我不关心这个词是否有效。那么如何将这个要求添加到编辑距离。
【问题讨论】:
标签: algorithm string transform
这可以建模为图形问题。您可以将单词视为图形的节点,当且仅当它们的长度相同且一个字符不同时,两个节点才连接。
您可以预处理字典并创建此图,应如下所示:
stack jack
| |
| |
smack back -- pack -- pick
然后你可以有一个从单词到代表单词的节点的映射,为此你可以使用哈希表,高度平衡 BST ...
一旦您有了上述映射,您所要做的就是查看两个图节点之间是否存在路径,这可以使用 BFS 或 DFS 轻松完成。
所以你可以将算法总结为:
preprocess the dictionary and create the graph.
Given the two inputs words w1 and w2
if length(w1) != length(w2)
Not possible to convert
else
n1 = get_node(w1)
n2 = get_node(w2)
if(path_exists(n1,n2))
Possible and nodes in the path represent intermediary words
else
Not possible
【讨论】:
codaddict 的图方法是有效的,尽管构建每个图需要 O(n^2) 时间,其中 n 是给定长度的单词数。如果这是个问题,您可以更高效地构建bk-tree,这样就可以找到目标词具有给定编辑距离(在本例中为 1)的所有词。
【讨论】:
Map<String, Map<String, Set<String>>> 将(prefix, suffix) 映射到一组以prefix 开头的单词,之后有任何字母,然后以suffix 结尾。您可以在 O(nm^2) 时间内构建此结构,其中 n 是字典大小,m 是最大字长。平均每个单词大约 25 次操作。
创建一个图表,每个节点代表字典中的单词。在两个词节点之间添加一条边,如果它们对应的词的编辑距离为 1。那么所需的最小转换次数将是源节点和目标节点之间的最短路径长度。
【讨论】:
我不认为这是编辑距离。
我认为这可以使用图表来完成。只需从您的字典中构建一个图,然后尝试使用您最喜欢的图遍历算法导航到目的地。
【讨论】:
您可以简单地使用递归回溯,但这远非最佳解决方案。
# Given two words of equal length that are in a dictionary, write a method to transform one word into another word by changing only
# one letter at a time. The new word you get in each step must be in the
# dictionary.
# def transform(english_words, start, end):
# transform(english_words, 'damp', 'like')
# ['damp', 'lamp', 'limp', 'lime', 'like']
# ['damp', 'camp', 'came', 'lame', 'lime', 'like']
def is_diff_one(str1, str2):
if len(str1) != len(str2):
return False
count = 0
for i in range(0, len(str1)):
if str1[i] != str2[i]:
count = count + 1
if count == 1:
return True
return False
potential_ans = []
def transform(english_words, start, end, count):
global potential_ans
if count == 0:
count = count + 1
potential_ans = [start]
if start == end:
print potential_ans
return potential_ans
for w in english_words:
if is_diff_one(w, start) and w not in potential_ans:
potential_ans.append(w)
transform(english_words, w, end, count)
potential_ans[:-1]
return None
english_words = set(['damp', 'camp', 'came', 'lame', 'lime', 'like'])
transform(english_words, 'damp', 'lame', 0)
【讨论】:
@Codeaddict 解决方案是正确的,但它错过了简化和优化解决方案的机会。
DFS 与 BFS:
如果我们使用 DFS,我们有可能在图中更深地遇到 target 字符串(或 to_string)。然后我们必须跟踪找到它的级别和对该节点的引用,最后找到可能的最低级别,然后从根开始跟踪它。
例如,考虑这个转换from -> zoom:
from
/ \
fram foom
/ \ / \
dram drom [zoom] food << To traverse upto this level is enough
... | ...
doom
|
[zoom]
使用 BFS,我们可以大大简化这个过程。我们需要做的就是:
from 级别的0 字符串开头。将此字符串添加到visitedSetOfStrings。visitedSetOfStrings。target 字符串,则停止进一步处理节点/字符串。否则继续第 2 步。为了使路径追踪更容易,我们可以在每个节点中添加parent字符串的额外信息。
【讨论】:
这是使用 BFS 解决问题的 C# 代码:
//use a hash set for a fast check if a word is already in the dictionary
static HashSet<string> Dictionary = new HashSet<string>();
//dictionary used to find the parent in every node in the graph and to avoid traversing an already traversed node
static Dictionary<string, string> parents = new Dictionary<string, string>();
public static List<string> FindPath(List<string> input, string start, string end)
{
char[] allcharacters = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
foreach (string s in input)
Dictionary.Add(s);
List<string> currentFrontier = new List<string>();
List<string> nextFrontier = new List<string>();
currentFrontier.Add(start);
while (currentFrontier.Count > 0)
{
foreach (string s in currentFrontier)
{
for (int i = 0; i < s.Length; i++)
{
foreach (char c in allcharacters)
{
StringBuilder newWordBuilder = new StringBuilder(s);
newWordBuilder[i] = c;
string newWord = newWordBuilder.ToString();
if (Dictionary.Contains(newWord))
{
//avoid traversing a previously traversed node
if (!parents.Keys.Contains(newWord))
{
parents.Add(newWord.ToString(), s);
nextFrontier.Add(newWord);
}
}
if (newWord.ToString() == end)
{
return ExtractPath(start, end);
}
}
}
}
currentFrontier.Clear();
currentFrontier.Concat(nextFrontier);
nextFrontier.Clear();
}
throw new ArgumentException("The given dictionary cannot be used to get a path from start to end");
}
private static List<string> ExtractPath(string start,string end)
{
List<string> path = new List<string>();
string current = end;
path.Add(end);
while (current != start)
{
current = parents[current];
path.Add(current);
}
path.Reverse();
return path;
}
【讨论】:
我认为我们不需要图形或其他一些复杂的数据结构。我的想法是将字典加载为HashSet 并使用contains() 方法找出字典中是否存在该单词。
请检查此伪代码以了解我的想法:
Two words are given: START and STOP.
//List is our "way" from words START to STOP, so, we add the original word to it first.
list.add(START);
//Finish to change the word when START equals STOP.
while(!START.equals(STOP))
//Change each letter at START to the letter to STOP one by one and check if such word exists.
for (int i = 0, i<STOP.length, i++){
char temp = START[i];
START[i] = STOP[i];
//If the word exists add a new word to the list of results.
//And change another letter in the new word with the next pass of the loop.
if dictionary.contains(START)
list.add(START)
//If the word doesn't exist, leave it like it was and try to change another letter with the next pass of the loop.
else START[i] = temp;}
return list;
据我了解,我的代码应该是这样工作的:
输入:DAMP、LIKE
输出:DAMP、LAMP、LIMP、LIME、LIKE
输入:BACK、PICK
输出:BACK、PACK、PICK
【讨论】:
class Solution {
//static int ans=Integer.MAX_VALUE;
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
HashMap<String,Integer> h=new HashMap<String,Integer>();
HashMap<String,Integer> h1=new HashMap<String,Integer>();
for(int i=0;i<wordList.size();i++)
{
h1.put(wordList.get(i),1);
}
int count=0;
Queue<String> q=new LinkedList<String>();
q.add(beginWord);
q.add("-1");
h.put(beginWord,1);
int ans=ladderLengthUtil(beginWord,endWord,wordList,h,count,q,h1);
return ans;
}
public int ladderLengthUtil(String beginWord, String endWord, List<String> wordList,HashMap<String,Integer> h,int count,Queue<String> q,HashMap<String,Integer> h1)
{
int ans=1;
while(!q.isEmpty())
{
String s=q.peek();
q.poll();
if(s.equals(endWord))
{
return ans;
}
else if(s.equals("-1"))
{
if(q.isEmpty())
{
break;
}
ans++;
q.add("-1");
}
else
{
for(int i=0;i<s.length();i++)
{
for(int j=0;j<26;j++)
{
char a=(char)('a'+j);
String s1=s.substring(0,i)+a+s.substring(i+1);
//System.out.println("s1 is "+s1);
if(h1.containsKey(s1)&&!h.containsKey(s1))
{
h.put(s1,1);
q.add(s1);
}
}
}
}
}
return 0;
}
}
【讨论】:
这显然是一个排列问题。使用图表是多余的。问题陈述缺少一个重要的约束; 每个位置只能更改一次。这暗示了解决方案在 4 个步骤之内。现在需要决定的是替换操作的顺序:
操作 1 = 将“H”更改为“T”
Operation2 = 将“E”更改为“A”
Operation3 = 将“A”更改为“I”
Operation4 = 将“D”改为“L”
解决方案,操作序列,是字符串“1234”的某种排列,其中每个数字代表被替换字符的位置。例如“3124”表示首先我们应用操作3,然后操作1,然后操作2,然后操作4。在每一步,如果结果单词不在字典中,则跳到下一个排列。合理的琐碎。给任何人编码?
【讨论】: