Hard

题目描述

给定一个整数 n。

如果一个数满足以下条件,则称为特殊数:

  • 它是一个回文数。
  • 数字中的每个数字 k 都恰好出现 k 次。

返回严格大于 n 的最小特殊数。

示例 1:

输入:n = 2
输出:22
解释:22 是大于 2 的最小特殊数,因为它是回文数且数字 2 恰好出现 2 次。

示例 2:

输入:n = 33
输出:212
解释:212 是大于 33 的最小特殊数,因为它是回文数且数字 1 和 2 分别恰好出现 1 次和 2 次。

约束条件:

  • 0 <= n <= 10^15

提示:

  • 特殊数的数量很少,可以预处理它们。
  • 使用位掩码暴力枚举所有有效的数字 k 选择。
  • 生成前半部分数字的所有排列,然后镜像形成完整的回文数。
  • 对于每个 n,使用二分查找找到严格大于 n 的最小特殊数。

解题思路

这道题的关键是理解特殊数的性质和预处理的思路。

分析特殊数的特点:

  1. 必须是回文数
  2. 每个数字 k 必须恰好出现 k 次
  3. 数字 0 不能出现(因为 0 出现 0 次意味着不存在)

解题思路:

由于特殊数非常稀少,我们可以采用预处理的方法:

  1. 枚举数字组合:使用位掩码枚举所有可能的数字组合。对于数字 1-9,每个数字 k 要么不选,要么选择并出现 k 次。

  2. 构造回文数:对于每种有效的数字组合,需要构造回文数。关键是确定前半部分的排列,然后镜像形成完整回文。

  3. 排列生成:对于确定的数字组合,生成前半部分的所有可能排列。需要特别处理奇数长度的情况(中间数字只能是奇数次出现的数字)。

  4. 预处理所有特殊数:将所有可能的特殊数生成并排序。

  5. 二分查找:对于给定的 n,在预处理的特殊数数组中二分查找第一个大于 n 的数。

实现细节:

  • 使用回溯生成所有有效的数字排列
  • 注意处理奇数长度回文的中间位置
  • 确保生成的数字不超过 10^15 的范围

代码实现

class Solution {
public:
    vector<long long> specials;
    
    void generateSpecials() {
        // 使用位掩码枚举数字1-9的选择
        for (int mask = 1; mask < (1 << 9); mask++) {
            vector<int> digits;
            int totalCount = 0;
            
            // 根据掩码确定选择的数字
            for (int i = 0; i < 9; i++) {
                if (mask & (1 << i)) {
                    int digit = i + 1;
                    for (int j = 0; j < digit; j++) {
                        digits.push_back(digit);
                    }
                    totalCount += digit;
                }
            }
            
            // 生成回文数
            generatePalindromes(digits, totalCount);
        }
        
        sort(specials.begin(), specials.end());
    }
    
    void generatePalindromes(vector<int>& digits, int totalCount) {
        sort(digits.begin(), digits.end());
        
        // 奇数长度的情况
        if (totalCount % 2 == 1) {
            // 找到可以放在中间的数字(奇数次出现)
            map<int, int> count;
            for (int d : digits) count[d]++;
            
            for (auto& p : count) {
                if (p.second % 2 == 1) {
                    int middle = p.first;
                    vector<int> half;
                    for (auto& q : count) {
                        for (int i = 0; i < q.second / 2; i++) {
                            half.push_back(q.first);
                        }
                    }
                    generatePermutations(half, middle);
                    break;
                }
            }
        } else {
            // 偶数长度的情况
            vector<int> half;
            map<int, int> count;
            for (int d : digits) count[d]++;
            
            bool valid = true;
            for (auto& p : count) {
                if (p.second % 2 != 0) {
                    valid = false;
                    break;
                }
                for (int i = 0; i < p.second / 2; i++) {
                    half.push_back(p.first);
                }
            }
            
            if (valid) {
                generatePermutations(half, -1);
            }
        }
    }
    
    void generatePermutations(vector<int> half, int middle) {
        sort(half.begin(), half.end());
        do {
            if (half.empty() || half[0] != 0) {
                long long num = 0;
                
                // 前半部分
                for (int d : half) {
                    num = num * 10 + d;
                }
                
                // 中间数字
                if (middle != -1) {
                    num = num * 10 + middle;
                }
                
                // 后半部分(镜像)
                vector<int> reversed = half;
                reverse(reversed.begin(), reversed.end());
                for (int d : reversed) {
                    num = num * 10 + d;
                }
                
                if (num <= 1e15) {
                    specials.push_back(num);
                }
            }
        } while (next_permutation(half.begin(), half.end()));
    }
    
    long long specialPalindrome(long long n) {
        if (specials.empty()) {
            generateSpecials();
        }
        
        auto it = upper_bound(specials.begin(), specials.end(), n);
        return it != specials.end() ? *it : -1;
    }
};
class Solution:
    def __init__(self):
        self.specials = []
        self.generated = False
    
    def generateSpecials(self):
        from itertools import permutations
        
        # 枚举数字1-9的选择
        for mask in range(1, 1 << 9):
            digits = []
            total_count = 0
            
            # 根据掩码确定选择的数字
            for i in range(9):
                if mask & (1 << i):
                    digit = i + 1
                    digits.extend([digit] * digit)
                    total_count += digit
            
            # 生成回文数
            self.generatePalindromes(digits, total_count)
        
        self.specials.sort()
    
    def generatePalindromes(self, digits, total_count):
        from collections import Counter
        
        count = Counter(digits)
        
        if total_count % 2 == 1:
            # 奇数长度
            middle = -1
            for digit, cnt in count.items():
                if cnt % 2 == 1:
                    middle = digit
                    break
            
            if middle != -1:
                half = []
                for digit, cnt in count.items():
                    half.extend([digit] * (cnt // 2))
                self.generatePermutations(half, middle)
        else:
            # 偶数长度
            valid = True
            half = []
            for digit, cnt in count.items():
                if cnt % 2 != 0:
                    valid = False
                    break
                half.extend([digit] * (cnt // 2))
            
            if valid:
                self.generatePermutations(half, -1)
    
    def generatePermutations(self, half, middle):
        from itertools import permutations
        
        seen = set()
        for perm in permutations(half):
            if not perm or perm[0] != 0:
                perm_tuple = tuple(perm)
                if perm_tuple not in seen:
                    seen.add(perm_tuple)
                    
                    # 构造回文数
                    num_str = ''.join(map(str, perm))
                    if middle != -1:
                        num_str += str(middle)
                    num_str += ''.join(map(str, reversed(perm)))
                    
                    num = int(num_str)
                    if num <= 10**15:
                        self.specials.append(num)
    
    def specialPalindrome(self, n: int) -> int:
        if not self.generated:
            self.generateSpecials()
            self.generated = True
        
        import bisect
        idx = bisect.bisect_right(self.specials, n)
        return self.specials[idx] if idx < len(self.specials) else -1
public class Solution {
    private List<long> specials = new List<long>();
    private bool generated = false;
    
    private void GenerateSpecials() {
        // 枚举数字1-9的选择
        for (int mask = 1; mask < (1 << 9); mask++) {
            List<int> digits = new List<int>();
            int totalCount = 0;
            
            // 根据掩码确定选择的数字
            for (int i = 0; i < 9; i++) {
                if ((mask & (1 << i)) != 0) {
                    int digit = i + 1;
                    for (int j = 0; j < digit; j++) {
                        digits.Add(digit);
                    }
                    totalCount += digit;
                }
            }
            
            // 生成回文数
            GeneratePalindromes(digits, totalCount);
        }
        
        specials.Sort();
    }
    
    private void GeneratePalindromes(List<int> digits, int totalCount) {
        var count = new Dictionary<int, int>();
        foreach (int d in digits) {
            count[d] = count.GetValueOrDefault(d, 0) + 1;
        }
        
        if (totalCount % 2 == 1) {
            // 奇数长度
            int middle = -1;
            foreach (var pair in count) {
                if (pair.Value % 2 == 1) {
                    middle = pair.Key;
                    break;
                }
            }
            
            if (middle != -1) {
                List<int> half = new List<int>();
                foreach (var pair in count) {
                    for (int i = 0; i < pair.Value / 2; i++) {
                        half.Add(pair.Key);
                    }
                }
                GeneratePermutations(half, middle);
            }
        } else {
            // 偶数长度
            bool valid = true;
            List<int> half = new List<int>();
            foreach (var pair in count) {
                if (pair.Value % 2 != 0) {
                    valid = false;
                    break;
                }
                for (int i = 0; i < pair.Value / 2; i++) {
                    half.Add(pair.Key);
                }
            }
            
            if (valid) {
                GeneratePermutations(half, -1);
            }
        }
    }
    
    private void GeneratePermutations(List<int> half, int middle) {
        var permutations = GetPermutations(half);
        foreach (var perm in permutations) {
            if (perm.Count == 0 || perm[0] != 0) {
                long num = 0;
                
                // 前半部分
                foreach (int d in perm) {
                    num = num * 10 + d;
                }
                
                // 中间数字
                if (middle != -1) {
                    num = num * 10 + middle;
                }
                
                // 后半部分(镜像)
                for (int i = perm.Count - 1; i >= 0; i--) {
                    num = num * 10 + perm[i];
                }
                
                if (num <= 1000000000000000L) {
                    specials.Add(num);
                }
            }
        }
    }
    
    private HashSet<List<int>> GetPermutations(List<int> list) {
        var result = new HashSet<List<int>>(new ListComparer());
        var arr = list.ToArray();
        Array.Sort(arr);
        
        do {
            result.Add(new List<int>(arr));
        } while (NextPermutation(arr));
        
        return result;
    }
    
    private bool NextPermutation(int[] arr) {
        int i = arr.Length - 2;
        while (i >= 0 && arr[i] >= arr[i + 1]) i--;
        if (i < 0) return false;
        
        int j = arr.Length - 1;
        while (arr[j] <= arr[i]) j--;
        
        (arr[i], arr[j]) = (arr[j], arr[i]);
        Array.Reverse(arr, i + 1, arr.Length - i - 1);
        return true;
    }
    
    public long SpecialPalindrome(long n) {
        if (!generated) {
            GenerateSpecials();
            generated = true;
        }
        
        int left = 0, right = specials.Count - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (specials[mid] <= n) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        
        return left < specials.Count ? specials[left] : -1;
    }
}

public class ListComparer : IEqualityComparer<List<int>> {
    public bool Equals(List<int> x, List<int> y) {
        return x.SequenceEqual(y);
    }
    
    public int GetHashCode(List<int> obj) {
        return string.Join(",", obj).GetHashCode();
    }
}
var specialPalindrome = function(n) {
    function isSpecial(num) {
        const str = num.toString();
        const count = new Array(10).fill(0);
        
        for (let char of str) {
            count[parseInt(char)]++;
        }
        
        for (let i = 0; i < 10; i++) {
            if (count[i] !== 0 && count[i] !== i) {
                return false;
            }
        }
        
        return str === str.split('').reverse().join('');
    }
    
    function generateSpecialPalindromes(maxLen) {
        const results = [];
        
        function backtrack(digits, remaining) {
            if (remaining.every(x => x === 0)) {
                const palindrome = createPalindrome(digits);
                if (palindrome > n) {
                    results.push(palindrome);
                }
                return;
            }
            
            for (let digit = 1; digit <= 9; digit++) {
                if (remaining[digit] >= 2) {
                    digits.push(digit);
                    remaining[digit] -= 2;
                    backtrack(digits, remaining);
                    remaining[digit] += 2;
                    digits.pop();
                }
            }
            
            for (let digit = 1; digit <= 9; digit++) {
                if (remaining[digit] === 1) {
                    digits.push(digit);
                    remaining[digit] = 0;
                    backtrack(digits, remaining);
                    remaining[digit] = 1;
                    digits.pop();
                    break;
                }
            }
        }
        
        function createPalindrome(digits) {
            digits.sort((a, b) => a - b);
            const left = [];
            const right = [];
            let middle = -1;
            
            const count = new Array(10).fill(0);
            for (let d of digits) count[d]++;
            
            for (let i = 1; i <= 9; i++) {
                if (count[i] % 2 === 1) {
                    middle = i;
                }
                for (let j = 0; j < Math.floor(count[i] / 2); j++) {
                    left.push(i);
                }
            }
            
            const result = left.concat(middle === -1 ? [] : [middle], left.slice().reverse());
            return parseInt(result.join(''));
        }
        
        for (let len = 1; len <= maxLen; len++) {
            for (let mask = 1; mask < (1 << 9); mask++) {
                const remaining = new Array(10).fill(0);
                let totalDigits = 0;
                
                for (let digit = 1; digit <= 9; digit++) {
                    if (mask & (1 << (digit - 1))) {
                        remaining[digit] = digit;
                        totalDigits += digit;
                    }
                }
                
                if (totalDigits === len) {
                    backtrack([], remaining);
                }
            }
        }
        
        return results;
    }
    
    const nStr = n.toString();
    const candidates = generateSpecialPalindromes(nStr.length + 2);
    
    if (candidates.length === 0) {
        return generateSpecialPalindromes(20)[0];
    }
    
    return Math.min(...candidates);
};

复杂度分析

指标复杂度
时间-
空间-