1.概述
根据维基百科,一个Anagram是通过重新排列不同单词或短语的字母而形成的单词或短语。
我们可以在字符串处理中推广这一点,即字符串的一个anagram是另一个字符串,它中每个字符的数量完全相同,以任何顺序排列。
在本教程中,我们将研究如何检测整个字符串的anagram,其中每个字符的数量必须相等,包括空格和数字等非字母字符。给定两个字符串,每个字符包含中英文,空格,大小写等等,如果这两个字符串在忽略大小写和空格的情况下,如果相同字符出现的次数相同,则互为anagram。
2.解决方案
让我们比较几个可以确定两个字符串是否为anagram的解决方案。每个解决方案将在开始时检查两个字符串是否具有相同数量的字符。这是一种快速退出的方法,因为不同长度的输入不能被取消。
对于每个可能的解决方案,让我们来看看我们作为开发人员的实现复杂性。我们还将使用大O表示法来查看CPU的时间复杂度。
此解决方案易于理解和实现。但是,该算法的总体运行时间是O(n logn),因为对n个字符的数组排序需要O(n logn)时间。
要使算法正常工作,它必须使用一点额外的内存,将两个输入字符串复制为字符数组。
4.计数检查
另一种策略是计算输入中每个字符的出现次数。如果这些柱状图在输入之间相等,那么字符串就是anagrams。
为了节省一点内存,我们只构建一个直方图。我们将增加第一个字符串中每个字符的计数,并减少第二个字符串中每个字符的计数。如果这两个字符串是anagrams,那么结果就是所有的值都平衡到0。
直方图需要一个固定大小的计数表,其大小由字符集大小定义。例如,如果我们只使用一个字节来存储每个字符,那么我们可以使用256的计数数组来计数每个字符的出现次数:
随着O(n)时间复杂度的增加,该方法的求解速度更快。但是,它需要额外的空间用于计数数组。256个整数,对于ASCII来说还不错。
但是,如果我们需要增加CHARACTER_RANGE来支持多字节字符集,比如UTF-8,这将非常占用内存。因此,只有当可能的字符数在一个小范围内时,它才是真正实用的。
从开发的角度来看,这个解决方案包含了更多的代码需要维护,并且很少使用Java库函数。
5.用MultiSet来检查
利用MultiSet可以简化计算和比较过程。MultiSet是一个集合,它支持与重复元素无关的顺序相等。例如,多集{a,a,b}和{a,b,a}是相等的。
要使用Multiset,首先需要将番石榴依赖项添加到project pom.xml文件中:
我们将把每个输入字符串转换成多个字符集。然后我们会检查它们是否相等:
该算法在O(n)时间内解决了该问题,无需声明一个大的计数数组。
这与之前的计数方法类似。但是,我们没有使用固定大小的表来计数,而是利用MutlitSet类来模拟可变大小的表,每个字符都有一个计数。
此解决方案的代码比我们的计数解决方案更多地使用高级库功能。
6. 基于字母的Anagram
到目前为止,这些例子并没有严格遵循一个字谜的语言定义。这是因为他们认为标点符号是anagram的一部分,并且是区分大小写的。
让我们调整算法以启用基于字母的anagram。我们只考虑不区分大小写的字母的重新排列,而不考虑其他字符,如空格和标点符号。例如,“一个小数点”和“我是一个适当的点”是彼此之间的一个字谜。
为了解决这个问题,我们可以先对两个输入字符串进行预处理,过滤掉不需要的字符,然后将字母转换成小写字母。然后我们可以使用上面的一个解决方案(例如,MultiSet解决方案)来检查处理过的字符串上的anagram:
这种方法可以是一种通用的方法来解决所有的变型问题。例如,如果我们还想包含数字,我们只需要调整预处理过滤器。
7. 结论
在本文中,我们研究了三种算法来检查给定的字符串是否是另一个字符串的anagram,字符对字符。对于每个解决方案,我们讨论了速度、可读性和所需内存大小之间的权衡。
我们还研究了如何使算法适应更传统的语言意义上的语法检查。我们通过将输入预处理为小写字母来实现这一点。
与往常一样,本文的源代码可以在GitHub上找到。