#P2319. 第2题-排列组合的回文字符串
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 835
            Accepted: 127
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年6月19日-暑期实习
                              
                      
          
 
- 
                        算法标签>数学          
 
第2题-排列组合的回文字符串
题面描述:
塔子哥最近迷上了一个字符串重排的游戏。游戏规则是给定一个由小写字母组成的字符串 ( s ),通过重新排列 ( s ) 中的字母,看看最多能组成多少个不同的回文字符串。如果无法组成任何回文字符串,则输出 ( 0 )。请编写一个程序来实现这个功能。输入格式为一行包含字符串 ( s ),输出格式为一个整数,表示可以组成的不同回文字符串的数量。例如,输入 "aabb" 时,输出应为 ( 2 )。
思路
如果存在两个以上的个数为奇数的字符显然不能构成回文。
对于其他为偶数的情况,因为是回文串,所以我们只需考虑其中半边的情况即可。
现在问题变成了,有 K 种字母,每个字母的个数为 cnti 个,问有多少种情况
假设 K 种字母的和为 n
那么计算公式为:
对此我们只需预处理阶层即可 O(1) 计算单个结果,由于涉及到大数运算,这里采用 python 来实现。
关键点分析
- 
回文字符串的特性:
- 对于一个字符串能构成回文,最多只能有一个字符出现奇数次。因为在回文的中心可以放置这个字符,而其他字符必须成对出现。
 - 如果存在两个或以上的字符出现奇数次,则无法构成回文串,直接输出 ( 0 )。
 
 - 
字符计数:
- 统计字符串中每个字符的出现次数,分为奇数和偶数。
 - 只需要考虑每个字符的一半的数量来计算不同的回文串,因为回文的前半部分决定了整个回文。
 
 - 
排列计算:
- 计算回文的一半所需的字符的排列方式。假设我们有 ( K ) 种字符,每种字符的数量为 ( cnt_i ),我们需要计算总的排列数。
 - 总的排列数为所有字符的一半的总数的阶乘,然后除以每种字符的一半的数量的阶乘,以避免重复排列。
 
 
解决方案步骤
- 统计字符频率:使用哈希表统计每个字符的出现次数。
 - 判断奇数字符的数量:遍历统计结果,记录出现奇数次的字符数量。
- 如果奇数字符的数量大于 1,则输出 ( 0 )。
 
 - 计算字符的半数:将每个字符的计数除以 2,存储这些值。
 - 预处理阶乘:计算并存储从 ( 0 ) 到 ( n ) 的阶乘,以便后续快速计算。
 - 计算回文串的数量:根据公式计算可能的排列数,输出结果。
 
AC代码
Python
from collections import Counter
s = input().strip()
mp = Counter(s)  # 统计字符串中各个字符的个数
cnt = 0
v = []
# 统计奇数字符个数和偶数字符个数
for value in mp.values():
    if value % 2 == 1:
        cnt += 1
    v.append(value // 2)
# 如果奇数字符个数大于1,则无法构成回文串
if cnt > 1:
    print(0)
    exit(0)
# 计算回文串数量
total = sum(v)  # 总的字符数量的一半
f = [1] * (total + 1)  # 预处理阶乘值
for i in range(1, total + 1):
    f[i] = f[i - 1] * i
res = f[total]  # 计算全部字符的排列数
for i in v:
    res //= f[i]  # 除去各个字符的排列数
print(res)
java
import java.math.BigInteger;
import java.util.*;
public class Main {
    private static BigInteger palindromeNum(String str) {
        // 将字符串转换为字符数组
        char[] s = str.toCharArray();
        // 创建一个长度为26的数组,存储每个字母出现的次数
        int[] map = new int[26];
        for (char c : s) {
            map[c - 'a']++;
        }
        // 判断是否有超过一个的奇数字符
        boolean odd = false;
        for (int i = 0; i < 26; i++) {
            if (map[i] % 2 == 1) {
                if (odd) {
                    // 如果有超过一个奇数字符,则无法构成回文串,返回0
                    return new BigInteger("0");
                } else {
                    odd = true;
                }
            }
            // 将每个字母的个数除以2,因为回文串中每个字母出现的次数必须是偶数
            map[i] /= 2;
        }
        // 计算回文串的数量
        int n = s.length / 2;
        // 预先计算阶乘值,以便后续使用
        BigInteger[] f = new BigInteger[n + 1];
        f[0] = new BigInteger("1");
        for (int i = 1; i <= n; i++) {
            f[i] = f[i - 1].multiply(new BigInteger(String.valueOf(i)));
        }
        // 除去每种字符的排列数
        for (int i = 0; i < 26; i++) {
            if (map[i] > 0) {
                f[n] = f[n].divide(f[map[i]]);
            }
        }
        return f[n];
    }
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String s = in.nextLine();
        System.out.println(palindromeNum(s));
    }
}
C++
塔子哥的tips:这题确实对C++不公平。同样的思路却需要自己实现大整数,而其他语言都有对大整数模拟的封装。但是也没办法,因为C++实现大整数需要100+行代码。如果真的追求完美,C++这题起码写200行。所以碰到这种出题人,只能认栽。写个正常的算法看看能过多少就算了。赶快去其他题拿分。
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
int main() {
    string s;
    cin >> s;
    // 统计字符串中各个字符的个数
    unordered_map<char, int> mp;
    for (char c : s) {
        mp[c]++;
    }
    int cnt = 0;
    vector<int> v;
    // 统计奇数字符个数和偶数字符个数
    for (auto [_, value] : mp) {
        if (value % 2 == 1) {
            cnt++;
        }
        v.push_back(value / 2);
    }
    // 如果奇数字符个数大于1,则无法构成回文串
    if (cnt > 1) {
        cout << 0 << endl;
        return 0;
    }
    // 计算回文串数量
    int total = 0;
    for (int x : v) {
        total += x;
    }
    vector<long long> f(total + 1, 1);  // 预处理阶乘值
    for (int i = 1; i <= total; i++) {
        f[i] = f[i - 1] * i;
    }
    long long res = f[total];  // 计算全部字符的排列数
    for (int x : v) {
        res /= f[x];  // 除去各个字符的排列数
    }
    cout << res << endl;
    return 0;
}
javaScript
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
// 计算回文排列数量
function palindromeNum(str) {
    let s = str.split("");
    let map = Array(26).fill(0);
    for (let c of s) {
        map[c.charCodeAt(0) - 'a'.charCodeAt(0)]++;
    }
    let odd = false;
    for (let i = 0; i < 26; i++) {
        if (map[i] % 2 === 1) {
            if (odd) return BigInt(0); // 超过一个奇数字符,无法构成回文
            odd = true;
        }
        map[i] = Math.floor(map[i] / 2);
    }
    let n = Math.floor(s.length / 2);
    let f = Array(n + 1).fill(BigInt(1));
    for (let i = 1; i <= n; i++) {
        f[i] = f[i - 1] * BigInt(i);
    }
    let result = f[n];
    for (let i = 0; i < 26; i++) {
        if (map[i] > 0) {
            result /= f[map[i]];
        }
    }
    return result;
}
// 处理输入
(async function () {
    let s = await readline();
    console.log(palindromeNum(s).toString()); // 输出结果
    rl.close();
})();
        问题描述
小明最近迷上了一个字符串重排的游戏。游戏规则如下:给定一个由小写字母组成的字符串 s,通过重新排列 s 中的字母,看看最多能组成多少个不同的回文字符串。回文字符串是指一个字符串正着读和反着读完全一样,例如"level"和"noon"都是回文字符串,而"code"不是。
请你帮小明写一个程序,计算给定字符串 s 经过重排后最多能组成的不同回文字符串数量。
输入格式
输入一行,包含一个由小写字母组成的字符串 s,表示小明手中的初始字符串。
输出格式
输出一个整数,表示字符串 s 经过重排后最多能组成的不同回文字符串数量。如果无法组成任何回文字符串,则输出 0。
样例输入
aabb
样例输出
2
评测数据与规模
- 1≤∣s∣≤1000
 - 字符串 s 只包含小写字母