【问题标题】:Efficient algorithm for phrase anagrams短语字谜的高效算法
【发布时间】:2016-11-15 09:40:50
【问题描述】:

在给定字符串的情况下,生成短语字谜的有效方法是什么?

我要解决的问题

假设您有一个包含 n 个单词的单词列表。给定一个输入字符串,比如“peanutbutter”,生成所有短语字谜。一些竞争者是:花生酱、A But Ten Erupt 等。

我的解决方案

我有一个包含给定单词列表中所有单词的 trie。给定一个输入字符串,我计算它的所有排列。对于每个排列,我都有一个递归解决方案(类似于this)来确定该特定排列的字符串是否可以分解为单词。例如,如果花生酱的排列之一是“abuttenerupt”,我用这种方法将其分解为“a but ten erupt”。我使用 trie 来确定字符串是否为有效单词。

什么很糟糕

我的问题是,因为我计算了所有排列,所以对于超过 10 个字符的短语,我的解决方案运行速度非常慢,这是一个很大的失望。我想知道是否有办法以不同的方式做到这一点。 像https://wordsmith.org/anagram/ 这样的网站可以在不到一秒钟的时间内完成这项工作,我很想知道他们是如何做到的。

【问题讨论】:

    标签: string algorithm anagram


    【解决方案1】:

    您的问题可以分解为 2 个子问题:

    1. 查找用完输入字符串所有字符的单词组合
    2. 查找在第一个子问题中找到的单词的所有排列

    子问题 #2 是一种基本算法,您可以在大多数编程语言中找到现有的标准实现。让我们关注子问题#1

    首先将输入字符串转换为“字符池”。我们可以将字符池实现为数组oc,其中oc[c] = 字符c的出现次数。

    然后我们使用回溯算法来查找适合字符池的单词,如以下伪代码所示:

    result = empty;
    
    function findAnagram(pool)
      if (pool empty) then print result;
      for (word in dictionary) {
        if (word fit in charpool) {
          result = result + word;
          update pool to exclude characters in word;
          findAnagram(pool);
    
          // as with any backtracking algorithm, we have to restore global states
          restore pool;
          restore result;
        }
      }
    }
    

    注意:如果我们按值传递 charpool,那么我们不必恢复它。但由于它很大,我更喜欢通过引用传递它。

    现在我们删除冗余结果并应用一些优化:

    • 假设 A 在字典中排在 B 之前。如果我们选择第一个词是 B,那么我们不必在接下来的步骤中考虑词 A,因为那些结果(如果我们取 A)已经在选择 A 作为第一个词的情况下

    • 如果字符集足够小(

    更新伪代码以反映这些优化:

    function findAnagram(charpool, minDictionaryIndex)
      pool_bitmask <- bitmask(charpool);
      if (pool empty) then print result;
      for (word in dictionary AND word's index >= minDictionaryIndex) {
        // bitmask of every words in the dictionary should be pre-calculated
        word_bitmask <- bitmask(word)
        if (word_bitmask contains bit(s) that is not in pool_bitmask)
          then skip this for iteration
        if (word fit in charpool) {
          result = result + word;
          update charpool to exclude characters in word;
          findAnagram(charpool, word's index);
    
          // as with any backtracking algorithm, we have to restore global states
          restore pool;
          restore result;
        }
      }
    }
    

    我的子问题 #1 的 C++ 实现,其中字符集仅包含小写字母 'a'..'z':http://ideone.com/vf7Rpl

    【讨论】:

      【解决方案2】:

      您可以通过在递归生成排列时检查有效单词来加快速度,而不是生成排列然后尝试将它们分解为单词的两阶段解决方案。如果在任何时候您当前的部分完整排列不对应任何有效单词,请停在那里并且不要进一步递归。这意味着您不会浪费时间生成无用的排列。例如,如果生成“tt”,则无需排列“peanubuter”并将所有排列附加到“tt”,因为没有以tt开头的英文单词。

      假设您正在进行基本的递归排列生成,请跟踪您生成的当前部分单词。如果在任何时候它是一个有效的单词,您可以输出一个空格并开始一个新单词,并递归地排列剩余的字符。您还可以尝试将剩余的每个字符添加到当前的部分单词中,并且只有在这样做会导致有效的部分单词(即存在以这些字符开头的单词)时才会递归。

      类似这样的东西(伪代码):

       void generateAnagrams(String partialAnagram, String currentWord, String remainingChars)
       {
            // at each point, you can either output a space, or each of the remaining chars:
      
            // if the current word is a complete valid word, you can output a space
            if(isValidWord(currentWord))
            {
                 // if there are no more remaining chars, output the anagram:
                 if(remainingChars.length == 0)
                 {
                     outputAnagram(partialAnagram);
                 }
                 else
                 {
                     // output a space and start a new word
                     generateAnagrams(partialAnagram + " ", "", remainingChars);
                 }
            }
      
            // for each of the chars in remainingChars, check if it can be
            // added to currentWord, to produce a valid partial word (i.e.
            // there is at least 1 word starting with these characters)
            for(i = 0 to remainingChars.length - 1)
            {
                char c = remainingChars[i];
                if(isValidPartialWord(currentWord + c)
                {
                    generateAnagrams(partialAnagram + c, currentWord + c,
                        remainingChars.remove(i));
                }
            }
       }
      

      你可以这样称呼它

       generateAnagrams("", "", "peanutbutter");
      

      您可以通过传递与当前部分完成的单词对应的 trie 中的节点以及将 currentWord 作为字符串传递来进一步优化此算法。这将使您的isValidPartialWord 检查更快。

      您可以通过更改isValidWord 检查来强制唯一性,以仅在单词与前一个单词输出相比按字母升序(大于或等于)字母顺序时才返回true。最后,您可能还需要再次检查是否有欺骗,以发现可以输出两个相同单词的情况。

      【讨论】:

      • 我认为使用部分词是个好主意。我想我可以在 trie 中查找:检查是否有一个以部分单词开头的单词。谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-03
      • 2011-11-03
      • 2011-01-17
      • 2013-11-06
      • 2012-11-21
      • 2012-05-25
      • 2010-09-08
      相关资源
      最近更新 更多