查找给定单词的字谜

时间:2012-09-18 12:46:53

标签: algorithm data-structures language-agnostic anagram

如果其中一个字符与另一个字的字符完全相同,则两个字是字谜。

示例:Anagram& Nagaram是字谜(不区分大小写)。

现在有很多类似的问题。找出两个字符串是否为字谜的几种方法是:

1) Sort字符串并进行比较。

2)为这些字符串创建frequency map并检查它们是否相同。

但是在这种情况下,我们会给出一个单词(为了简单起见,我们只假设一个单词,它只有单个单词anagrams),我们需要找到它的字谜。

我想到的解决方案是,我们可以为该单词生成所有排列,并检查字典中中存在哪些单词。但显然,这是非常低效的。是的,字典也可用。

那么我们有什么替代方案呢?

我也在类似的帖子中读到可以使用Tries完成某些事情,但是这个人没有解释算法是什么,为什么我们首先使用Trie,只提供了一个实现在Python或Ruby中也是如此。所以这并不是真的有用,这就是我创建这个新线程的原因。如果有人想要分享他们的实现(除了C,C ++或Java),那么也要解释它。

12 个答案:

答案 0 :(得分:72)

算法示例:

Open dictionary
Create empty hashmap H
For each word in dictionary:
  Create a key that is the word's letters sorted alphabetically (and forced to one case)
  Add the word to the list of words accessed by the hash key in H

检查给定单词的所有字谜:

Create a key that is the letters of the word, sorted (and forced to one case)
Look up that key in H
You now have a list of all anagrams

建立速度相对较快,查找速度极快。

答案 1 :(得分:17)

我想出了一个新的解决方案。它使用算术的基本定理。所以我的想法是使用前26个素数的数组。然后对于输入字中的每个字母,我们得到相应的素数A = 2,B = 3,C = 5,D = 7 ......然后我们计算输入字的乘积。接下来,我们对字典中的每个单词执行此操作,如果单词与输入单词匹配,则将其添加到结果列表中。所有字谜都有相同的签名,因为

  

任何大于1的整数都是素数,或者可以写   作为素数的唯一乘积(忽略顺序)。

这是代码。我将单词转换为大写,65是A的位置,对应于我的第一个素数:

private int[] PRIMES = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
        37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
        107, 109, 113 };

这是方法:

 private long calculateProduct(char[] letters) {
    long result = 1L;
    for (char c : letters) {
        if (c < 65) {
            return -1;
        }
        int pos = c - 65;
        result *= PRIMES[pos];
    }
    return result;
}

答案 2 :(得分:2)

我们知道,如果两个单词的长度不同,则它们不是字谜。因此,您可以将字典分成相同长度的单词组。

现在我们只关注其中一个组,基本上所有单词在这个较小的宇宙中都有完全相同的长度。

如果每个字母位置都是尺寸,则该尺寸中的值基于字母(例如ASCII代码)。然后你可以计算出单词向量的长度。

例如,说“A”= 65,“B”= 66,然后是length("AB") = sqrt(65*65 + 66*66)。显然,length("AB") = length("BA")

显然,如果两个单词是字谜,那么它们的向量具有相同的长度。接下来的问题是,如果两个单词(相同数量的字母)向量具有相同的长度,那么它们是字谜吗?直观地说,我会说不,因为所有具有该长度的矢量形成一个球体,所以有许多。不确定,因为在这种情况下我们处于整数空间中,实际上有多少。

但至少它允许你进一步划分字典。对于词典中的每个单词,计算向量的距离: for(each letter c) { distance += c*c }; distance = sqrt(distance);

然后为所有长度为n的单词创建一个地图,并用距离键入它,该值是一个长度为n的单词列表,可以产生该特定距离。

您将为每个距离创建一张地图。

然后您的查找将成为以下算法:

  1. 根据单词的长度
  2. 使用正确的字典映射
  3. 计算单词矢量的长度
  4. 查找与该长度匹配的单词列表
  5. 通过列表并使用天真算法选择字谜现在候选人名单大大减少

答案 3 :(得分:1)

Well Tries可以更容易地检查单词是否存在。 所以,如果你把整个字典放在一个特里:

http://en.wikipedia.org/wiki/Trie

然后你可以接受你的话并通过取一个字符进行简单的回溯并递归检查我们是否可以通过其余字符的任何组合来“走”Trie(一次添加一个字符)。当所有字符在递归分支中使用并且Trie中有一个有效路径时,则该单词存在。

Trie帮助因为它有一个很好的停止条件: 我们可以检查字符串的一部分,例如“Anag”是否是trie中的有效路径,如果不是,我们可以打破那个特定的递归分支。这意味着我们不必检查每个字符的排列。

在伪代码中

checkAllChars(currentPositionInTrie, currentlyUsedChars, restOfWord)
    if (restOfWord == 0)
    {
         AddWord(currentlyUsedChar)
    }
    else 
    {
        foreach (char in restOfWord)
        {
            nextPositionInTrie = Trie.Walk(currentPositionInTrie, char)
            if (nextPositionInTrie != Positions.NOT_POSSIBLE)
            {
                checkAllChars(nextPositionInTrie, currentlyUsedChars.With(char), restOfWord.Without(char))
            }
        }   
    }

显然你需要一个漂亮的Trie数据结构,它允许你逐步“走”到树下,并检查每个节点是否有给定char的路径到任何下一个节点......

答案 4 :(得分:1)

static void Main(string[] args)
{

    string str1 = "Tom Marvolo Riddle";
    string str2 = "I am Lord Voldemort";

    str2=  str2.Replace(" ", string.Empty);
    str1 = str1.Replace(" ", string.Empty);
    if (str1.Length != str2.Length)
        Console.WriteLine("Strings are not anagram");
    else
    {
        str1 = str1.ToUpper();
        str2 = str2.ToUpper();
        int countStr1 = 0;
        int countStr2 = 0;
        for (int i = 0; i < str1.Length; i++)
        {
            countStr1 += str1[i];
            countStr2 += str2[i];

        }
        if(countStr2!=countStr1)
            Console.WriteLine("Strings are not anagram");
        else Console.WriteLine("Strings are  anagram");

    }
    Console.Read();
}

答案 5 :(得分:1)

  • 将单词缩减为 - 例如 - 小写(clojure.string/lower-case)。
  • 按字母频率图(group-by)对它们进行分类(frequencies)。
  • 删除频率图,
  • ......留下字谜的集合。

These)是Lisp方言Clojure中的相应函数。

整个功能可以这样表达:

(defn anagrams [dict]
  (->> dict
       (map clojure.string/lower-case)
       (group-by frequencies)
       vals))

例如,

(anagrams ["Salt" "last" "one" "eon" "plod"])
;(["salt" "last"] ["one" "eon"] ["plod"])

将每个事物映射到其集合的索引函数是

(defn index [xss]
  (into {} (for [xs xss, x xs] [x xs])))

所以,例如,

((comp index anagrams) ["Salt" "last" "one" "eon" "plod"])
;{"salt" ["salt" "last"], "last" ["salt" "last"], "one" ["one" "eon"], "eon" ["one" "eon"], "plod" ["plod"]}

...其中comp是功能组合运算符。

答案 6 :(得分:0)

生成所有排列很容易,我猜你担心检查它们在字典中的存在是非常低效的#34;部分。但这实际上取决于您用于字典的数据结构:当然,单词列表对您的用例来说效率低下。说到Tries,它们可能是一个理想的代表,也非常有效。

另一种可能性是对您的字典进行一些预处理,例如:构建一个哈希表,其中键是单词的字母排序,值是单词列表。您甚至可以序列化此哈希表,以便将其写入文件并在以后快速重新加载。然后查找字谜,您只需对给定的单词进行排序,并在哈希表中查找相应的条目。

答案 7 :(得分:0)

这取决于你如何存储字典。如果它是一个简单的单词数组,那么任何算法都不会比线性更快。

如果它已经排序,那么这是一种可行的方法。我刚刚发明了它,但我想它比线性方法更快。

  1. 将您的字典表示为D,当前前缀为S. S = 0;
  2. 您可以为单词创建频率图。让我们用F。
  3. 来表示
  4. 使用二进制搜索查找指向字典中每个字母的开头的指针。让我们用P表示这个指针数组。
  5. 对于从A到Z的每个字符,如果F [c] == 0,则跳过它,否则
    • S + = c;
    • F [c] - ;
    • P&lt; - 对于每个字符i P [i] =指向以S + i开头的第一个单词的指针。
    • 递归调用第4步,直到找到与您的单词匹配或直到您发现不存在此类匹配为止。
  6. 无论如何,我就是这样做的。应该有一种更传统的方法,但这比线性更快。

答案 8 :(得分:0)

尝试实现hashmap解决方案

public class Dictionary {

    public static void main(String[] args){

    String[] Dictionary=new String[]{"dog","god","tool","loot","rose","sore"};

    HashMap<String,String> h=new HashMap<String, String>();

    QuickSort q=new QuickSort();

    for(int i=0;i<Dictionary.length;i++){

        String temp =new String();

        temp= q.quickSort(Dictionary[i]);//sorted word e.g dgo for dog

        if(!h.containsKey(temp)){
           h.put(temp,Dictionary[i]);
        }

        else
        {
           String s=h.get(temp);
           h.put(temp,s + " , "+ Dictionary[i]);
        }
    }

    String word=new String(){"tolo"};

    String sortedword = q.quickSort(word);

    if(h.containsKey(sortedword.toLowerCase())){ //used lowercase to make the words case sensitive

        System.out.println("anagrams from Dictionary   :  " + h.get(sortedword.toLowerCase()));
    }

}

答案 9 :(得分:0)

  • 计算字典中每个单词的频率计数向量,即字母表长度的向量。
  • 生成字母表列表长度的随机高斯向量
  • 在此随机方向投影每个字典单词的计数向量并存储该值(插入使得值数组被排序)。

  • 给定一个新的测试词,将其投影到用于字典词的相同随机方向。

  • 执行二进制搜索以查找映射到相同值的单词列表。
  • 验证上面获得的每个单词是否确实是真正的字谜。如果没有,请将其从列表中删除。
  • 返回列表的其余元素。

PS:上述程序是素数过程的推广,可能会导致大数(因此计算精度问题)

答案 10 :(得分:-3)

一个解决方案是 - 将素数映射到字母字符并乘以素数

For ex - 

    a -> 2
    b -> 3
    ......
    .......
    ......
    z -> 101

所以

'ab' -> 6
'ba' -> 6
'bab' -> 18
'abba' -> 36
'baba' -> 36

获取给定单词的MUL_number。返回字典中与给定单词

具有相同MUL_number的所有单词

答案 11 :(得分:-3)

首先检查字符串的长度是否相同。 然后检查两个字符串中的字符总和是否相同(即ascii代码总和) 然后这些词是字谜 否则不是anagram