小数学解决大问题 - 异构词问题 Anagrams(由素数的性质想到)

前言

异构词来源于英文单词Anagrams。它只包含相同字母(以及相同出现次数)的单词,例如era和are是一对异构词,因为他们都包含a,r,e三个字母。
关于Anagrams的定义可以查看http://en.wikipedia.org/wiki/Anagram。从这篇博客的题目或许很难看出异构词与素数之间的联系,这篇文章就来演示一个特别的问题解决方法。


异构词问题

问题描述

输入两个字符串,判断是否是异构词。

备注:其他类似应用的题目在leetcode出现过,http://oj.leetcode.com/problems/anagrams/

问题思路

首先,我们来考虑一下常规的解决思路:


思路1:两层循环遍历
假设输入的两个字符串各有n个字符,我们可以通过两层循环,将字符两两比较,找出相同的字符个数。当相同的字符个数等于n时,两个词为异构词;否则不是。这个方法的问题是时间复杂度非常高,达到了O(n2)。并且当某个字符出现多次时,代码需要考虑多种特殊情况,实现也比较复杂。


思路2:Hash表
介于“空间换时间”的思路,最简单的办法即利用Hash表存储字符的情况,再将两个字符串比较。假设我们认为字符串中只会出现字母,考虑大小写一共54种取值,因此我们可以定义一个整形数组 int hash[54],并且将54个字母(大小写)映射入该Hash表,如‘a’对应0,’z’对应25,‘A’对应26,’Z’对应53。
第一步是遍历第一个字符串,将每个字符对应的Hash表取值加1。
第二部是遍历第二个字符串,如果某个字符对应的Hash表取值为0,说明两词不是异构词;否则,对该位置减1。
该方法的时间复杂度为O(n),同时空间复杂度为O(n)。


接下来隆重出现的是利用素数性质的方法了。那么,我们需要用到什么样的素数性质呢?最基本的,素数指在一个大于1的自然数中,除了1和此整数自身外,不能被其他自然数(不包括0)整除的数。推广开来,我们可以假设我们有a,b,c三个素数,a的n次方乘以b的m次方是不能被c整除的。很好,有了这个性质我们就可以考虑用一个整数来表示一个单词,而不是空间复杂度O(n)的Hash表了。


操作过程很简单,我们找出多个不同的素数,将每个素数对应一个字母。而一个字符串(不考虑顺序)可以表示为每个字符串对应的素数的乘积。例如,a用2表示,b用3表示,c用5表示。那么abc可以用30来表示,aabbacc可以用1800来表示。


有了这个方法,我们将可以通过两部遍历来找到异构词:
第一步是遍历第一个字符串,将每个字符对应的素数相乘。
第二步是遍历第二个字符串,将每个字符对应的素数相乘。
最后若两个结果相等,则两词为异构词。(有一点细节的优化是这第二步是采用前一步的结果除以每个字符对应的素数,若不整除时两词肯定不是异构,便可以提前结束循环)
时间复杂度为O(n),空间复杂度为O(1)


备注:关于leetcode异构词的题目可以参考我的博客:http://blog.csdn.net/lanxu_yy/article/details/17374967


问题总结

本文,我们通过字符串来举例,实际上对于两个数组判断是否元素相同而顺序不同时也是可以使用的。(时间复杂度比排序再比较的O(nlogn)小)但是本方法有一些问题,第一,我们需要事先知道可能的取值范围;第二,整数的范围有限;第三,代码的可读性不如Hash表直观。


无论如何,我们这里主要是想说明数学解决实际问题的一些方法。

点赞