字符串数组只包含字谜?
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
可能会得到显着改进:
HashMap<char, int>
有一种方法可以在 HashMap
中指定默认值,还有一种方法来获取和递增(因此只有一个查找而不是 2 个)
但是,这些不在标准库中,所以我忽略了它们(就像大多数使用 Java 的程序员一样)。
这个故事的寓意是,大 O 并不是一切。您需要考虑开销和 n 的大小。在这种情况下,n 相当小,HashMap
的开销很大。对于更长的线路,这可能会改变,但不幸的是,我不想弄清楚盈亏平衡点在哪里。
如果你仍然不相信我,考虑一下 GCC 在其 C++ 标准库中的某些情况下使用 insertion sort。
【讨论】:
排序只能比明显的O(n)算法慢。 @nibot - 我看不出反对票是为了什么。他们想知道其他选择,这是另一种选择。这个选项几乎可以保证使用更少的内存,并且根据字符串的长度和您的散列函数,它也可以更快。大 O 不是一切。 我会说这是最好的解决方案,因为它非常简单,而且它需要的内存最少 :) @nibot:你让你的生活变得轻松......我们不是排序行,我们正在对每一行进行排序。这对应于将字符串映射到等价类 w.r.t。排列 -> 字谜。此外,由于字母表非常有限,您可以通过使用基数排序或简单的分箱来获得普遍喜爱的线性时间。 @Stephen C - 我打电话给System.gc()
就在我开始计时。 “计时器”(start
到 end
)内唯一的东西是 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<Character, Integer>
表示字符数。
创建对应的工厂接口。
在使用命令行参数、区域设置或其他方式表示键的两种方式之间进行选择。
注意事项:
目前尚不清楚“字谜”在非字母语言的上下文中是否有意义,或者对于将多种语言混合成“句子”的话语是否有意义。另外,我不知道(比如说)法语中的字谜是否忽略了字符的重音。无论如何,我很想将所有这些情况都视为“超出范围”......除非您明确要求支持它们。
int[]
使用的空间少于HashMap<Character, Integer>
的收支平衡密度在计数数组中的字符范围内逐渐接近于 15 个字符中的 1 个字符。 (具有这些键/值类型的 HashMap 中的每个条目都占用 15 个 32 位字的区域。)这还没有考虑到 HashMap
节点和哈希数组节点的开销......
如果您对字谜的长度进行了限制,您可以使用short[]
甚至byte[]
来计算字符数,从而节省更多空间。
【讨论】:
以上是关于字符串数组只包含字谜?的主要内容,如果未能解决你的问题,请参考以下文章