题目描述:

回文字符串是指从左到右和从右到左相同的字符串,现给定一个仅由小写字母组成的字符串,你可以把它的字母重新排列,以形成不同的回文字符串。

输入:非空仅由小写字母组成的字符串,长度不超过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;    
}