↓快轉到主要內容
  1. LeetCode/

LeetCode 3518: Smallest Palindromic Rearrangement II

·2 分鐘· ·
LeetCode Hard Math Combinatorics Counting Hash-Table String
Wei Yi Chung
作者
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
目錄

基本資料
#

難易度: hard 第一次嘗試:2025-04-13

  • 提交紀錄:WA → TLE ×2(2025-04)→ WA → TLE → RE → TLE → AC → RE → AC(2026-09-26)

解題思路
#

s 本身是回文,所以每個字元的數量除了最多一個以外都是偶數。回文由左半邊決定(右半邊是鏡像,中間是出現奇數次的那個字元),題目變成:左半邊這個多重集合的第 k 個排列。

逐位決定:

  • 剩下的排列數 = 剩下長度! / Π 每個字元剩下的數量!。
  • 這一位試字元 c(由小到大):放 c 之後的排列數是 目前排列數 × cnt[c] / 剩下長度。
    • k 落在這個分支裡(k <= 這個數):放 c,往下一位。
    • 否則 k 減掉這個數,試下一個字元。
  • 一開始總排列數 < k 就回傳空字串。

我的嘗試
#

時間版本問題
2025-04WA只算了一次 next permutation,沒用到 k。例如 s = "cc"、k = 6:只有一種回文排列,答案是空字串,回傳 "cc"
2025-04兩次 TLE改成跑 k - 1 次 next permutation,k 到 10^6 太慢
2026-09WA改成逐位決定,但中間字元只看「數量 < 2 的字元」,出現 3 次的字元沒被當成中間字元。例如 s = "ccc"、k = 1:答案是 "ccc",回傳 "cc"
2026-09TLE ×2每試一個字元都重新算一次 factorial 的大數除法
2026-09RE ×2函式名稱改成了 kthSmallestPalindrome,LeetCode 找不到 smallestPalindrome
2026-09AC排列數改成 O(1) 更新:branch = current × cnt[c] // length

正確解法
#

from math import factorial

class Solution:
    def smallestPalindrome(self, s: str, k: int) -> str:
        cnt = [0] * 26
        for ch in s:
            cnt[ord(ch) - 97] += 1
        mid = ""
        for i in range(26):
            if cnt[i] % 2:
                mid = chr(i + 97)                  # 出現奇數次的那個(包含 3、5 次)
            cnt[i] //= 2                           # 左半邊的數量

        length = sum(cnt)
        ways = factorial(length)
        for c in cnt:
            ways //= factorial(c)
        if ways < k:
            return ""

        left = []
        for _ in range(length):
            for i in range(26):
                if cnt[i] == 0:
                    continue
                branch = ways * cnt[i] // length   # 這一位放 i 之後的排列數
                if k <= branch:
                    left.append(chr(i + 97))
                    cnt[i] -= 1
                    ways, length = branch, length - 1
                    break
                k -= branch
        half = "".join(left)
        return half + mid + half[::-1]

複雜度
#

時間 O(26 · n) 次大整數運算(加上一開始算階乘),空間 O(n)。也可以在計算排列數時把超過 k 的值截斷成 k,避免大整數。

要特別避免的錯誤
#

  • 中間字元是出現奇數次的字元,不是出現 1 次的。
  • 一個一個列舉排列:k 很大時太慢。
  • 每次都重算階乘:用比例 cnt[c] / length 更新就好。

面試口說整理
#

回文由左半邊決定,所以題目變成左半邊多重集合的第 k 個排列。我逐位決定:試每個字元,放下去之後剩下的排列數是目前排列數乘 cnt[c] 除以剩下長度,k 在這個範圍內就放它,否則扣掉再試下一個。中間字元是出現奇數次的那個。時間 O(26n)。

相關文章

LeetCode 1814: Count Nice Pairs in an Array
·1 分鐘
LeetCode Medium Array Hash-Table Math Counting
LeetCode 1814 解題筆記:把條件移項成 nums[i] - rev(nums[i]) == nums[j] - rev(nums[j]),按這個值分組數 pair,記得取模
LeetCode 1189: Maximum Number of Balloons
·1 分鐘
LeetCode Easy Hash-Table String Counting
LeetCode 1189 解題筆記:每個字母可以組幾個 balloon 是 次數 // 需要數,l 和 o 需要兩個,取最小值
LeetCode 914: X of a Kind in a Deck of Cards
·1 分鐘
LeetCode Easy Array Hash-Table Math Counting
LeetCode 914 解題筆記:每種卡片的張數都要能被同一個 X >= 2 整除,也就是所有張數的 GCD >= 2
LeetCode 884: Uncommon Words from Two Sentences
·1 分鐘
LeetCode Easy Hash-Table String Counting
LeetCode 884 解題筆記:把兩個句子合起來數每個單字的次數,只出現一次的就是答案
LeetCode 819: Most Common Word
·1 分鐘
LeetCode Easy Array Hash-Table String Counting
LeetCode 819 解題筆記:標點換成空白、全部轉小寫再切單字,計數後找出不在 banned 裡的最多的那個
LeetCode 383: Ransom Note
·1 分鐘
LeetCode Easy Hash-Table String Counting
LeetCode 383 解題筆記:ransomNote 每個字元的需求數量都不能超過 magazine 的數量;Counter 相減為空就成立