5

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

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

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

程序应该返回 True。对于输入:

出租车契​​约 Huffiest
Minnows
Loll

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

现在,我认为很简单:

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

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

任何意见?建议?优化?

非常感谢你的帮助。

4

3 回答 3

5

另一种选择是:

  1. 从字符串中删除您不关心的所有字符(标点符号、空格)
  2. 把它变成小写
  3. 对字符串进行排序
  4. 与参考字符串比较(带.equals

我怀疑你的方式更快。

编辑:

由于@nibot 不同意我的建议,而且我不是一个在没有证据的情况下来回争论的人,这里有三个解决方案

它们的实现都非常相似:

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

这 ?部分是以下之一:

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

我用这个运行它们:

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条件,则可以显着改善:

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

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

如果您仍然不相信我,请考虑 GCC 在其 C++ 标准库中的某些情况下使用插入排序

于 2011-10-04T00:00:08.480 回答
3

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

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

于 2011-10-03T23:53:05.723 回答
2

以@Karl Knechtel 的回答为基础(并解决您对支持多个字母的担忧):

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

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

  • 创建使用 aHashMap<Character, Integer>表示字符数的 AnagramKey 接口的第二个实现。

  • 创建相应的工厂接口。

  • 在使用命令行参数、语言环境或其他方式表示键的两种方式之间进行选择。

笔记:

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

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

  3. 如果您对字谜的长度进行限制,则可以通过使用 ashort[]甚至 a来节省更多空间byte[]

于 2011-10-04T00:23:31.427 回答