短语字谜的高效算法

时间:2016-07-13 00:31:15

标签: string algorithm anagram

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

我想解决的问题

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

我的解决方案

我有一个包含给定单词列表中所有单词的trie。给定输入字符串,我计算它的所有排列。对于每个排列,我有一个递归解决方案(类似this)来确定特定的置换字符串是否可以分解为单词。例如,如果花生酱的一种排列是“邻接的”,我就用这种方法将其分解为“十次爆发”。我使用trie来确定字符串是否是有效字。

糟透了什么

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

2 个答案:

答案 0 :(得分:3)

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

  1. 查找用尽输入字符串所有字符的单词组合
  2. 查找第一个子问题中找到的单词的所有排列
  3. 子问题#2是一​​种基本算法,您可以在大多数编程语言中找到现有的标准实现。让我们关注子问题#1

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

    然后我们使用回溯算法来查找适合在charpool中的单词,就像在这个伪代码中一样:

    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被选为第一个单词的情况下

    • 如果字符集足够小(最好是<64个字符),我们可以使用位掩码快速过滤不适合池中的单词。一个位掩码掩码,无论出现多少次,该字符都在一个单词中。

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

    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 ++实现,其中字符集仅包含小写&#39; ...&#39; z&#39;:http://ideone.com/vf7Rpl

答案 1 :(得分:1)

您可以通过在递归生成排列时检查有效单词来代替生成排列然后尝试将其分解为单词的两阶段解决方案。如果在任何时候您当前的部分完成排列与任何有效单词不对应,请停在那里并且不再进一步递归。这意味着您不会浪费时间来产生无用的排列。例如,如果您生成&#34; tt&#34;,则无需置换&#34;花生&#34;并将所有排列附加到&#34; tt&#34;因为没有以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来强制执行唯一性。您可能还需要在最后检查dupes,以捕获可以输出两个相同单词的情况。