3517. Smallest Palindromic Rearrangement I
題目 / Problem
中文: 給你一個回文字串 s(正著讀和倒著讀完全一樣,例如 "aba"、"abba")。你需要把它的字母重新排列,得到一個同樣是回文、而且字典序最小的字串,並回傳它。所謂「字典序最小」,就是像字典排序那樣,越靠前的位置字母越小越好(a < b < c < ...)。
English: You are given a palindromic string s (it reads the same forwards and backwards, e.g. "aba", "abba"). Rearrange its letters to form another palindrome that is lexicographically smallest, and return it. "Lexicographically smallest" means: compare like dictionary order — make the earliest positions hold the smallest possible letters (a < b < c < ...).
Constraints / 限制:
- 1 <= s.length <= 10^5
- s 只由小寫英文字母組成 / s consists of lowercase English letters.
- s 保證是回文 / s is guaranteed to be palindromic.
Worked example / 範例: s = "babab" → output "abbba". The letters {a, a, b, b, b} rearranged into the smallest palindrome give "abbba".
名詞解釋 / Glossary
- 回文 / Palindrome: 正著讀和倒著讀一樣的字串。/ A string that reads identically forwards and backwards, e.g.
"abcba". - 字典序 / Lexicographic order: 像字典排列單字的順序:先比第一個字母,相同再比第二個,依此類推;字母越小排越前。/ Dictionary-style ordering: compare position by position; a smaller letter earlier makes the whole string smaller.
- 頻率陣列 / Frequency (count) array: 一個大小為 26 的整數陣列,
freq[c]記錄字母c出現幾次。因為只有 26 個小寫字母,用固定陣列比雜湊表更快更省。/ A size-26 integer array wherefreq[c]stores how many times lettercappears — perfect for 26 lowercase letters. - 雙指針 / Two pointers: 用兩個索引(一個從左往右
left,一個從右往左right)同時往中間移動,一次處理回文的兩個對稱位置。/ Two indices moving toward the center, filling the two mirror positions of the palindrome at once. - 中間字元 / Middle character: 當字串長度為奇數時,正中央那一格。回文中只有它(也只能有它)可以有奇數個。/ For odd-length strings, the exact center slot — the only character allowed to have an odd count.
malloc/ 動態配置記憶體: C 語言中向系統要一塊記憶體來存結果字串;LeetCode 要求回傳的字串必須用malloc配置。/ Allocates memory at runtime for the result string, as LeetCode's C harness expects.
思路
中文: 先想暴力法:把所有排列都列出來,篩掉不是回文的,再取字典序最小的。但長度可達 $10^5$,排列數是天文數字,完全不可行。所以我們要直接「構造」答案。關鍵觀察:回文由兩個互為鏡像的半邊組成——左半邊決定後,右半邊就是它的反轉,中間可能有一個單獨字元。因為右半邊完全由左半邊鏡射而來,要讓整個字串字典序最小,只要讓左半邊字典序最小即可。那左半邊怎麼最小?把每個字母的數量除以二(回文中每個字母成對出現,一半放左、一半放右),然後從 a 到 z 由小到大依序填入左半邊,自然就是最小的。長度為奇數時,會恰好有一個字母出現奇數次,把它放到正中間(它放哪都不影響左半邊排序,放中間最不影響字典序)。實作上用雙指針:left 從頭、right 從尾,依字母順序同時填兩端,保證鏡像對稱;最後若有奇數字元填入中央。這樣一次掃描就完成,$O(n)$。
English: Brute force — enumerate all permutations, keep the palindromic ones, pick the smallest — is hopeless: with length up to $10^5$ the number of permutations is astronomical. Instead we construct the answer directly. The key insight (from the hints): a palindrome is two mirror-image halves plus an optional single middle character. Since the right half is forced to be the reverse of the left half, minimizing the whole string reduces to minimizing the left half. To make the left half smallest, we halve each letter's count (letters come in pairs in a palindrome — one of each pair goes left, its mirror goes right) and lay them out from a up to z. If the length is odd, exactly one letter has an odd count; that leftover goes in the exact center, where it can't hurt the left-half ordering. We implement this with two pointers: left fills from the front and right from the back, placing the same letter at both ends in alphabetical order to keep the mirror symmetry. One pass, $O(n)$.
逐步走查 / Walkthrough
Example input s = "babab" (length n = 5).
Step 0 — Count / 計數頻率:
| letter | a | b |
|---|---|---|
| freq | 2 | 3 |
We allocate res of size 5, set pointers left = 0, right = 4, and mid = -1.
Step 1 — process letter a (freq 2, even) / 處理 a:
- half = 2/2 = 1. Place one a at both ends.
- res[0]='a', res[4]='a' → res = "a...a"
- left = 1, right = 3.
Step 2 — process letter b (freq 3, odd) / 處理 b:
- Count is odd → remember mid = 'b' (this letter goes in the center).
- half = 3/2 = 1. Place one b at both ends.
- res[1]='b', res[3]='b' → res = "ab.ba"
- left = 2, right = 2.
Step 3 — fill the middle / 填中間:
- mid != -1, so res[left] = res[2] = 'b'.
- res = "abbba" ✅
Final answer: "abbba", matching the expected output.
Solution — C
// 演算法:統計每個字母出現次數,取一半由小到大填入兩端(雙指針鏡射),
// 奇數次的字母放正中央,即得字典序最小的回文。
// Algorithm: count letters; place half of each (a→z) at both ends via two
// pointers (mirror), put the odd-count letter in the center.
char* smallestPalindrome(char* s) {
int n = strlen(s); // n 是字串長度 / n = length of the string
int freq[26] = {0}; // 26 個字母的計數,全部初始化為 0 / count of each letter, all zero
// 掃一遍統計每個字母出現幾次 / one pass to count each letter
for (int i = 0; i < n; i++)
freq[s[i] - 'a']++; // s[i]-'a' 把 'a'..'z' 映成 0..25 當索引 / map letter to index 0..25
// 向系統要 n+1 個位元組:n 個字元 + 1 個結尾 '