题目描述:
回文字符串是指从左到右和从右到左相同的字符串,现给定一个仅由小写字母组成的字符串,你可以把它的字母重新排列,以形成不同的回文字符串。
输入:非空仅由小写字母组成的字符串,长度不超过100;
输出:能组成的所有回文串的个数(因为结果可能非常大,输出对1000000007取余数的结果)。
例如:输入"aabb" 输出为2(因为“aabb”对应的所有回文字符串有2个:abba和baab)
思路:
问题需要我们给出可行解,那么就需要梳理出清晰准确的因果关系:
在这26个字母的序列中,
Case<1>如果有大于1个元素的个数是奇数,那么无论如何都构不成回文
Case<2>如果有且仅有1个元素的个数是奇数,把它放在正中央,还是可以构成回文数的
Case<3>如果所有元素的个数都是偶数,亦可构成回文数
本质上可以把Case<1>和Case<2>当成同一情况予以处理
细节1:
对于可行的字符串,如何计算种数?
e.g. aaaabbcccddddee
因为回文串具有对称性这一性质,所以只需要考虑一半的元素:aabcdde(多余的C放中央,不需考虑)
需要对其进行全排列,上例就是7!,显然这7!包含重复的序列,如何归为正解呢?应该除以每个元素个数一半的阶乘,原因自己考虑
设有arr[26],存放的是每个元素出现的个数,M为字符串的元素总数
那么ans = (M/2)!/(arr[0]/2)!*(arr[1]/2)!*…(arr[25]/2)!
细节2:
如果取最大字符串,长度为100,我们就需计算50!
而以下数据类型的存储范围是:
int: 2147483647 (10位)------------12! = 479001600
long long(__int64):9223372036854775807 (19位)------------21! = 5.1090942e+19
所以如果暴力计算50!会溢出的
应对方法:对于ans = (M/2)!/(arr[0]/2)!*(arr[1]/2)!*…(arr[25]/2)!
不必先乘完再除,边乘边除
Source Code:
#include<stdio.h> #include<string.h> int arr[26]; char s[102]; int palindrome(const char *s) { int i,j,tmp,sum,item,death,len,count = 0; long long mul; memset(arr,0,sizeof(arr)); len = strlen(s);
for(i = 0; i < len; i++) arr[s[i]-97] += 1; for(i = 0; i < 26 ;i++){ tmp = arr[i]; if(tmp % 2 == 1) count++; if(tmp == 0 || tmp == 1) continue; sum = 1; for(j = 2; j <= tmp/2; j++) sum *= j; arr[i] = sum; }
if (count > 1) return 0; mul = 1,item = 0;
for(i = 1; i <= len/2; i++){ while((arr[item] == 0 || arr[item] == 1) && item < 26) item++; mul *= i; if( item < 26 && mul % arr[item] == 0){ mul /= arr[item]; item++; } if(item >= 26){ death = i; break; } } for(i = death+1; i<= len/2; i++){ mul = (mul*i) % 1000000007; } return mul; } int main(){ while(scanf("%s",s) != EOF){ printf("%d\n",palindrome(s)); } return 0; }