本文要讨论的是变位词,也就是说通过交换一个单词的各个字母的顺序能变成另一个单词,那么这两个单词互为变位词。
问题一:判断给定的两个单词(标准ASCII)是否变位词
方法一:用两个数组分别统计两个字符串里每个字符出现的个数,如果完全一致,则是变位词,否则不是.
这个方法是大小写敏感的。这个方法能支持的输入字符串的最大长度 将受 用来统计字符个数的数组的类型 的限制。如果用unsigned char[128]来统计字符个数,unsigned char最大值是255(0xFF),那么能支持的输入字符串最大长度是255。对于长度大于255的字符串将有可能出现误判。比如分别由256个"a"和256个"b"组成的两个字符串,统计结果都是全0,程序将误判为是变位词。如果改为用unsigned int[128],则能支持的输入字符串的最大长度是0xFFFFFFFF。但占用的内存是前者的4倍。
另外,下面的实现中将统计结果数组定义为static。理由一,不在调用栈上,可以避免在占用有限的stack空间,理由二,也没有在堆上,避免该函数被频繁调用的时候会产生大量的空间申请和释放的操作。(这两个理由成立吗?充分吗?)
bool IsAnagram1(const char *pStr1, const char *pStr2)
{
static int stat1[128] = {0};
static int stat2[128] = {0};
memset(stat1, 0, sizeof(stat1));
memset(stat2, 0, sizeof(stat1));
const char *p = pStr1;
while (*p != '\0') {
stat1[*p]++;
p++;
}
p = pStr2;
while (*p != '\0') {
stat2[*p]++;
p++;
}
return (0 == memcmp(stat1, stat2, sizeof(stat1)));
}
方法二:利用这个原理:两个素数集合所含的素数和每个素数的个数都相等 当且仅当 每个集合所有元素的乘积相等。所以,令每个字符对应一个素数,分别计算每个字符串中所有字符所对应的素数的乘积。如果两个乘积相等,则是变位词。这个方法会受到数据最大值的限制。假设输入字符仅包含a-z的26个字符(大小写不敏感),则为了区分这26个字符,需要用到26个素数( 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101)分别对应a到z。最大值是101。另外我们需要一个足够大的数据类型来正确保存素数乘积,double型最大值为1.79769e+308。如果输入字符全部为对应素数101的那个字符z,那么最多可以支持153个。101^153小于DBL_MAX,101^154大于DBL_MAX。也就是说如果某个字符串含有154个z,那么素数乘积将越界,结果无法判断。
bool IsAnagram2(const char *pStr1, const char *pStr2)
{
static const int PrimeNumber[26] = {2, 3, 5, 7, 11, 13, 17, 19, 23,
29, 31, 37, 41, 43, 47, 53, 59,
61, 67, 71, 73, 79, 83, 89, 97, 101};
double f1 = 1.0;
const char *pa = pStr1;
while (*pa != '\0') {
if (*pa >='A' && *pa <='Z')
f1 *= PrimeNumber[*pa + ('a'-'A') - 'a'];
else
f1 *= PrimeNumber[*pa - 'a'];
pa++;
}
double f2 = 1.0;
const char *pb = pStr2;
while (*pb != '\0') {
f2 *= PrimeNumber[*pb - 'a'];
pb++;
}
return f1 == f2;
}
对方法二的一种可能的改进是借用其他能够支持大数计算的Lib来完成素数的乘积。本文不再给出代码。
另一种改进可以将字符分组,分别判断两个字符串所含每一组字符是否一致。两个字符串是变位词 当且仅当 字符串所含每一组字符所对应的素数的乘积都分别相等。这样,可以增加能够判断的字符串的长度和字符范围。比如按大小写分组,即可实现26*2种不同字符组成的字符串的变位词判断,支持最大字符长度153(下面的代码给出了这个实现)。或者仍然不区分大小写,将26个字母分为a-m和n-z两组,每组13个字符,用到前13个素数,第13个素数是41,double最大值可以表示41的最高次方是191次方。即可实现26种不同字符组成的字符串的变位词判断,支持最大字符长度191。
bool IsAnagram3(const char *pStr1, const char *pStr2)
{
static const int PrimeNumber[26] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67,
71, 73, 79, 83, 89, 97, 101};
double fUpcase1 = 1.0;
double fLowCase1 = 1.0;
const char *p = pStr1;
while (*p != '\0') {
if (*p >='A' && *p <='Z')
fUpcase1 *= PrimeNumber[*p -'A'];
else
fLowCase1 *= PrimeNumber[*p - 'a'];
p++;
}
double fUpcase2 = 1.0;
double fLowCase2 = 1.0;
p = pStr2;
while (*p != '\0') {
if (*p >='A' && *p <='Z')
fUpcase2 *= PrimeNumber[*p -'A'];
else
fLowCase2 *= PrimeNumber[*p - 'a'];
p++;
}
return (fLowCase1 == fLowCase2) && (fUpcase1 == fUpcase2);
}
问题二:输出给定单词的所有变位词
待续