最小回文排列的解法

题目描述

给你一个回文字符串 s,返回它的按字典序最小的回文排列

  • 回文:从前往后和从后往前读都一样。
  • 排列:字符串中所有字符的重排。
  • 字典序:从第一个字符开始比,谁先小谁就小;前面对完都一样,短的更小。(力扣 3517)

解决方法

1. 常见的想法

把所有回文排列都列出来,排个序,取第一个?No,能解决,但是——

给你二十个字符串让你全排列,你不崩溃,计算机也要崩溃了。暴力当然可解,前提是数据量够小。

2. 换个思路:只看一半

既然是回文,左右两半肯定一样,我们只关心一半就行。

那先统计每个字母出现了几次,一个循环搞定。

int cnt[26] = {0};
for (char c : s) cnt[c - 'a']++;
// 遍历 s,把每个字符转成 0~25 的索引,计数

每个字符都至少出现两次?No——出现奇数次的字符只能有一个(它正好放正中间)。比如 ababa:a 出现 3 次,b 出现 2 次,拿一个 a 当中间,剩下两个 a 两个 b。

3. 拼出左半部分

把每个字符频次的一半按字母序拼起来,得到左半部分 half,顺便记下中间那个奇数字符。

string half;
char mid = 0;
for (int i = 0; i < 26; ++i) {
    half.append(cnt[i] / 2, 'a' + i);   // 频次一半个 'a'+i 拼进 half
    if (cnt[i] % 2 == 1) mid = 'a' + i; // 奇数次的那个字符当中间
}

这样 half 天然就是字典序最小的左半部分。

4. 生成右半部分

half 反转就是右半部分,回文就拼成了。

string right = half;
reverse(right.begin(), right.end());

if (mid) return half + mid + right; // 有中间字符
return half + right;                // 没有中间字符

完整代码如下

#include <string>
#include <algorithm>
using namespace std;

class Solution {
public:
    string smallestPalindrome(string s) {
        int cnt[26] = {0};
        for (char c : s) cnt[c - 'a']++;

        string half;
        char mid = 0;
        for (int i = 0; i < 26; ++i) {
            half.append(cnt[i] / 2, 'a' + i);
            if (cnt[i] % 2 == 1) mid = 'a' + i;
        }

        string right = half;
        reverse(right.begin(), right.end());

        if (mid) return half + mid + right;
        return half + right;
    }
};