字符串数组只包含字谜?

Posted

技术标签:

【中文标题】字符串数组只包含字谜?【英文标题】:Array of Strings contains only anagrams? 【发布时间】:2011-11-30 07:29:14 【问题描述】:

我得到了一个关于字谜的练习,它看起来非常简单,以至于我怀疑我错过了一些东西。 我实施的解决方案是我将很快介绍的解决方案,我想问您是否可以考虑我的解决方案的任何优化、方法的改变或问题。 我用 Java 实现了算法。

现在,练习。 作为输入,我有一个文本,作为输出,我应该返回该文本的每一行是否是另一行的字谜。 也就是说,对于输入:

出租车契约 Huffiest Minnows Loll 出租车契​​约 Huffiest Minnow Lolls 出租车契​​约洗牌百万元 出租车契​​约洗牌百万镇

程序应该返回 True。 输入:

出租车契约 Huffiest Minnows Loll 出租车契​​约 Huffiest Minnow Lolls hi 出租车契​​约洗牌百万元 出租车契​​约洗牌百万镇

输出必须为 False(当然是因为第二行)。

现在,我的想法很简单:

我创建了 2 个 HashMap:ref 和 cur。 我解析文本的第一行,填充参考。我只会计算字母。 对于每一行,我将行解析为 cur 并检查 cur.equals(ref): if so return false 如果我到了文本的末尾,则表示每一行都是其他行的字谜,所以我返回 true。

而且……就是这样。 我用 88000 行的输入文本进行了尝试,它的运行速度非常快。

有没有cmets?建议?优化?

非常感谢您的帮助。

【问题讨论】:

【参考方案1】:

另一种选择是:

    从字符串中删除所有您不关心的字符(标点符号、空格) 小写 对字符串进行排序 与参考字符串比较(使用.equals

我怀疑你的方式更快。

编辑:

由于@nibot 不同意我的建议,而且我不会在没有证据的情况下来回争论,here's three solutions。

它们的实现都非常相似:

    将行转换为小写 忽略非字母字符 ? 检查 3. 的结果与第一行的结果相匹配

那个?部分是以下之一:

创建HashMap 的字符数 对字符进行排序 制作一个 26 整数数组(最终的哈希表解决方案,但仅适用于拉丁字母)

我都用这个运行了它们:

public static void time(String name, int repetitions, Function function,
        int expectedResult) throws Exception 
    long total = 0;
    for (int i = 0; i < repetitions; i++) 
        System.gc();
        long start = System.currentTimeMillis();
        int result = function.call();
        long end = System.currentTimeMillis();
        if (result != expectedResult) 
            System.out.println("Oops, " + name + " is broken");
            return;
        
        total += end - start;
    
    System.out.println("Executution of " + name + " took "
            + (total / repetitions) + " ms on average");

我的文件与 OP 发布的文件相似,但要长得多,在末尾有大约 20 行的非字谜,以确保算法都能正常工作。

我总是得到这样的结果:

Execution of testWithHashMap took 158 ms on average
Execution of testWithSorting took 76 ms on average
Execution of testWithArray took 56 ms on average

如果满足以下条件,HashMap 可能会得到显着改进:

There was a way to make a HashMap&lt;char, int&gt; 有一种方法可以在 HashMap 中指定默认值,还有一种方法来获取和递增(因此只有一个查找而不是 2 个)

但是,这些不在标准库中,所以我忽略了它们(就像大多数使用 Java 的程序员一样)。

这个故事的寓意是,大 O 并不是一切。您需要考虑开销和 n 的大小。在这种情况下,n 相当小,HashMap 的开销很大。对于更长的线路,这可能会改变,但不幸的是,我不想弄清楚盈亏平衡点在哪里。

如果你仍然不相信我,考虑一下 GCC 在其 C++ 标准库中的某些情况下使用 insertion sort

【讨论】:

排序只能比明显的O(n)算法慢。 @nibot - 我看不出反对票是为了什么。他们想知道其他选择,这是另一种选择。这个选项几乎可以保证使用更少的内存,并且根据字符串的长度和您的散列函数,它也可以更快。大 O 不是一切。 我会说这是最好的解决方案,因为它非常简单,而且它需要的内存最少 :) @nibot:你让你的生活变得轻松......我们不是排序行,我们正在对每一行进行排序。这对应于将字符串映射到等价类 w.r.t。排列 -> 字谜。此外,由于字母表非常有限,您可以通过使用基数排序或简单的分箱来获得普遍喜爱的线性时间。 @Stephen C - 我打电话给System.gc() 就在我开始计时。 “计时器”(startend)内唯一的东西是 function.call()(可能还有调用 System.currentTimeMillis() 的一些开销)。 但另一方面是您正在从算法中完全消除 GC 的(可能的)开销/影响。那也是不现实的。正确的做法是调用 System.gc()。然后以大量重复运行测试,丢弃前几个(以处理“JVM 预热”效果),然后取平均值以消除 GC“块”。总之,这仍然是一个不可靠的基准。【参考方案2】:

假设您的 HashMap 是从(字符)->(字符串中出现的次数)的映射,那么您几乎拥有它。

我假设您应该忽略空格和标点符号,并将大写和小写字母视为相同。如果您不使用英语以外的任何语言,那么 HashMap 就大材小用了:您可以简单地使用代表 A..Z 的 26 个计数的数组。如果您需要支持 Unicode,那么问题当然要复杂得多,因为您不仅需要处理可能数千种不同类型的字母,而且您还必须定义“字母”(幸运的是存在字符属性数据对此有帮助)和“小写/大写”(请注意,有些语言没有大小写,有些可以将两个小写字母映射为一个大写字母,反之亦然......)。更不用说标准化了:)

【讨论】:

好吧,我考虑了 26 个字符的数组,但正如你提到的,hashmap 实现更“可扩展”,以防需要添加英文字母以外的字符。但我不知道这是否真的有意义....我会考虑的!谢谢!【参考方案3】:

根据@Karl Knechtel 的回答(并解决您对支持多个字母的担忧):

创建接口(例如)AnagramKey 和 AnagramKeyFactory。将应用程序的其余部分设计为与所使用的密钥类型无关。

创建 AnagramKey 接口的一个实现,该接口在内部使用 int[] 来表示字符数。

创建 AnagramKey 接口的第二个实现,该接口使用 HashMap&lt;Character, Integer&gt; 表示字符数。

创建对应的工厂接口。

在使用命令行参数、区域设置或其他方式表示键的两种方式之间进行选择。

注意事项:

    目前尚不清楚“字谜”在非字母语言的上下文中是否有意义,或者对于将多种语言混合成“句子”的话语是否有意义。另外,我不知道(比如说)法语中的字谜是否忽略了字符的重音。无论如何,我很想将所有这些情况都视为“超出范围”......除非您明确要求支持它们。

    int[] 使用的空间少于HashMap&lt;Character, Integer&gt; 的收支平衡密度在计数数组中的字符范围内逐渐接近于 15 个字符中的 1 个字符。 (具有这些键/值类型的 HashMap 中的每个条目都占用 15 个 32 位字的区域。)这还没有考虑到 HashMap 节点和哈希数组节点的开销......

    如果您对字谜的长度进行了限制,您可以使用short[] 甚至byte[] 来计算字符数,从而节省更多空间。

【讨论】:

以上是关于字符串数组只包含字谜?的主要内容,如果未能解决你的问题,请参考以下文章

需要帮助来理解字谜代码的乐趣

从数组中删除仅大小写不同的字符串值(Ruby)

在给定的字符串列表中查找字符串的所有字谜

插入数组的额外字符

提高 anagram string 函数的时间效率

分开不同的字谜