通过有效词将一个词转换成另一个词的算法

时间:2010-02-05 07:02:55

标签: algorithm string transform

我遇到了edit-distance问题的变体:

设计一种将源词转换为目标词的算法。例如:从头到尾,在每一步中,你只需要替换一个字符,并且该字必须有效。你会得到一本字典。

它显然是edit distance问题的变体,但在编辑距离中我不关心这个词是否有效。那么如何添加此要求来编辑距离。

10 个答案:

答案 0 :(得分:43)

这可以建模为图形问题。您可以将这些单词视为图形的节点,并且当且仅当它们具有相同的长度并且在一个char中不同时,才连接两个节点。

您可以预处理字典并创建此图表,如下所示:

   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

答案 1 :(得分:10)

codaddict的图方法是有效的,尽管构建每个图需要O(n ^ 2)时间,其中n是给定长度的字数。如果这是一个问题,您可以更有效地构建bk-tree,这样就可以找到目标词的给定编辑距离(在本例中为1)的所有单词。

答案 2 :(得分:3)

创建一个图表,每个节点代表字典中的单词。如果两个字节点的编辑距离为1,则在两个字节点之间添加边。然后,所需的最小转换次数将是源节点和目标节点之间的最短路径长度。

答案 3 :(得分:2)

我认为这不是编辑距离。

我认为这可以使用图表完成。只需从字典中构建一个图形,并尝试使用您喜欢的图形遍历算法导航到目的地。

答案 4 :(得分: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)

答案 5 :(得分:0)

这是使用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;
    }

答案 6 :(得分:0)

我认为我们不需要图表或其他复杂的数据结构。我的想法是将字典加载为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

答案 7 :(得分: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,我们可以大大简化此过程。我们要做的就是:

  1. from级别0字符串开始。将此字符串添加到visitedSetOfStrings
  2. 将未访问的有效字符串添加到下一级别,该字符串与当前级别的字符串的编辑距离为+1。
  3. 将所有这些字符串添加到visitedSetOfStrings
  4. 如果该集合包含target字符串,请停止对节点/字符串的进一步处理。否则,请继续执行步骤2。

为了简化路径跟踪,我们可以在每个节点中添加parent字符串的额外信息。

答案 8 :(得分:-1)

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;    
    }
}

答案 9 :(得分:-2)

这显然是一种排列问题。使用图表是过度的。问题陈述缺少一个重要的约束; 您只能更改每个职位一次 。这使得它隐含了解决方案在4个步骤内。现在需要决定的是替换操作的顺序:

Operation1 =将“H”改为“T”
Operation2 =将“E”改为“A”
Operation3 =将“A”改为“I”
Operation4 =将“D改为”L“

解决方案,即操作序列,是字符串“1234”的一些排列,其中每个数字代表被替换字符的位置。例如“3124”表示首先应用operation3,然后是operation1,然后是operation2,然后是操作4.在每一步,如果结果单词不在字典中,则跳到下一个排列。合理的琐碎。编码任何人吗?