2026-01-15:下一个特殊回文数。用go语言,给定一个整数 n,求出一个比 n 更大的最小整数,该整数需要满足两条规则:
-
它的十进制表示从左到右与从右到左完全一致(即读起来是对称的)。
-
对于每一个数字 k(0–9),如果该数字出现在这个数中,那么它恰好出现 k 次;不出现的数字则出现次数为 0。
返回满足这两条条件且严格大于 n 的最小整数。
0 <= n <= 1000000000000000。
输入: n = 2。
输出: 22。
解释:
22 是大于 2 的最小特殊数,因为它是一个回文数,并且数字 2 恰好出现了 2 次。
题目来自力扣3646。
解决步骤分析
-
初始化与预处理
- 将输入的整数
n转换为字符串s,以便于逐位处理。 - 计算字符串的长度
m,并确定回文数的“中间位置”mid,其值为(m - 1) / 2。这对于后续生成回文数至关重要。 - 初始化一个长度为10的计数器数组
cnt,用于记录当前考虑的数字组合中,每个数字(0-9)计划出现的次数。初始时,计数器会根据当前数字的前半部分进行设置。
- 将输入的整数
-
检查当前数字本身
- 首先,程序会尝试基于
n本身构造一个回文数。这是通过取n的前半部分(包括中间数字,如果长度是奇数的话),然后将其反转并拼接在后面,从而形成一个完整的回文数pal。 - 接着,程序会验证这个回文数
pal是否大于原始的n,并且其数字出现次数是否符合规则(通过valid函数检查计数器cnt)。如果符合,则直接返回pal作为结果。 - 如果
n的长度是奇数,还会特别尝试增大中间位的数字,看看是否能得到符合条件的更大回文数。
- 首先,程序会尝试基于
-
寻找更大的候选数(核心逻辑)
- 如果基于
n的当前结构找不到更大的特殊回文数,程序会从右向左依次尝试增大回文数左半部分的数字。这是一个贪心策略的体现,旨在用尽可能小的增量找到一个更大的数。 - 关键函数
solve:对于在位置i尝试填入一个更大的数字j的情况,这个函数负责构造出字典序最小的可能回文数。- 可行性检查:它首先检查在剩余的固定数字位确定后,需要“自由填充”的位数是多少。同时,它会验证当前的数字出现次数规则是否可能被满足(例如,每个数字k的计划出现次数不能超过k,所有数字的总出现次数必须等于回文数长度等)。如果不可能,则放弃此分支。
- 背包问题求解:为了以最小的字典序填充剩余的自由位,该问题被转化为一个动态规划(0-1背包)问题。目标是选择一组数字(每个数字代表需要成对出现,因为回文数是对称的),使得它们的“代价”之和正好等于需要自由填充的位数。这里追求的是字典序最小解。
- 构造回文数:根据背包问题求解出的数字组合,以及必须出现的数字,程序会构造出回文数左半部分(包括中间位),然后通过镜像对称生成完整的回文数字符串,并转换为整数。
- 如果基于
-
递增数位长度
- 如果即使在当前数字长度
m下尝试所有可能后仍未找到符合条件的回文数,程序会递归地调用自身,但参数变为10^m(即比当前n多一位的最小整数,如100、1000等),在新的、更长的数字范围内继续寻找下一个特殊回文数。
- 如果即使在当前数字长度
复杂度分析
- 总的时间复杂度:该算法的时间复杂度主要受数字长度
m和数字基数(10进制)的影响。动态规划部分处理每位数字的复杂度与m和可选的数字状态相关。综合来看,最坏情况下的时间复杂度可以表示为 O(m² * K),其中K是一个与数字组合可能性相关的常数因子。 - 总的额外空间复杂度:空间消耗主要用于存储计数器数组、动态规划表以及中间生成的字符串。这些存储需求与数字长度
m成正比。因此,总的额外空间复杂度为 O(m)。
Go完整代码如下:
package main
import (
"bytes"
"fmt"
"math"
"slices"
"strconv"
)
// 从 a 中选一个字典序最小的、元素和等于 target 的子序列
// a 已经从小到大排序
// 无解返回 nil
func zeroOneKnapsack(a []int, target int) []int {
n := len(a)
f := make([][]bool, n+1)
for i := range f {
f[i] = make([]bool, target+1)
}
f[n][0] = true
// 倒着 DP,这样后面可以正着(从小到大)选
for i := n - 1; i >= 0; i-- {
v := a[i]
for j := range f[i] {
if j < v {
f[i][j] = f[i+1][j]
} else {
f[i][j] = f[i+1][j] || f[i+1][j-v]
}
}
}
if !f[0][target] {
return nil
}
ans := []int{}
j := target
for i, v := range a {
if j >= v && f[i+1][j-v] {
ans = append(ans, v)
j -= v
}
}
return ans
}
func specialPalindrome(num int64) int64 {
s := strconv.FormatInt(num, 10)
m := len(s)
mid := (m - 1) / 2
const mx = 10
cnt := make([]int, mx)
for _, d := range s[:mid+1] {
cnt[d-'0'] += 2
}
valid := func() bool {
for i, c := range cnt {
if c > 0 && c != i {
return false
}
}
return true
}
// 首先,单独处理中间位置
tmp := []byte(s[:m/2])
slices.Reverse(tmp)
pal, _ := strconv.ParseInt(s[:mid+1]+string(tmp), 10, 64)
if m%2 == 0 {
// 不修改
if pal > num && valid() {
return pal
}
} else {
// 修改正中间
cnt[s[mid]-'0'] -= 2
for j := s[mid] - '0'; j < mx; j++ {
cnt[j]++
if pal > num && valid() {
return pal
}
cnt[j]--
pal += int64(math.Pow10(m / 2))
}
}
// 下标 i 填 j,正中间填 midD(如果 m 是偶数则 midD 是 0)
solve := func(i int, j, midD byte) int64 {
// 中间 [i+1, m-2-i] 需要补满 0 < cnt[k] < k 的数字 k,然后左半剩余数位可以随便填
free := m/2 - 1 - i // 统计左半(不含正中间)可以随便填的数位个数
odd := 0
for k, c := range cnt {
if k < c { // 不合法
free = -1
break
}
if c > 0 {
odd += k % 2
free -= (k - c) / 2
}
}
if free < 0 || odd > m%2 {
return -1
}
// 对于可以随便填的数位,计算字典序最小的填法
a := []int{}
for k := 2; k < mx; k += 2 {
if cnt[k] == 0 {
a = append(a, k/2) // 左半需要填 k/2 个数
}
}
missing := zeroOneKnapsack(a, free)
if missing == nil {
return -1
}
for _, v := range missing {
cnt[v*2] = -v * 2 // 用负数表示可以随便填的数
}
t := []byte(s[:i+1])
t[i] = '0' + j
for k, c := range cnt {
if c > 0 {
c = k - c
} else {
c = -c
cnt[k] = 0 // 还原
}
d := []byte{'0' + byte(k)}
t = append(t, bytes.Repeat(d, c/2)...) // 只考虑左半
}
right := slices.Clone(t)
slices.Reverse(right)
if midD > 0 {
t = append(t, '0'+midD)
}
t = append(t, right...)
ans, _ := strconv.ParseInt(string(t), 10, 64)
return ans
}
// 从右往左尝试
for i := m/2 - 1; i >= 0; i-- {
cnt[s[i]-'0'] -= 2 // 撤销
// 增大 s[i] 为 j
for j := s[i] - '0' + 1; j < mx; j++ {
cnt[j] += 2
if m%2 == 0 {
ans := solve(i, j, 0)
if ans != -1 {
return ans
}
} else {
ans := int64(math.MaxInt)
// 枚举正中间填 d
for d := byte(1); d < mx; d += 2 {
cnt[d]++
res := solve(i, j, d)
if res != -1 {
ans = min(ans, res)
}
cnt[d]--
}
if ans != math.MaxInt {
return ans
}
}
cnt[j] -= 2
}
}
// 没找到,返回长为 m+1 的最小回文数
return specialPalindrome(int64(math.Pow10(m)))
}
func main() {
n := int64(2)
result := specialPalindrome(n)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
import math
from typing import List, Optional
def zero_one_knapsack(a: List[int], target: int) -> Optional[List[int]]:
"""从a中选一个字典序最小的、元素和等于target的子序列,a已经从小到大排序"""
n = len(a)
# 创建DP表
f = [[False] * (target + 1) for _ in range(n + 1)]
f[n][0] = True
# 倒着DP
for i in range(n - 1, -1, -1):
v = a[i]
for j in range(target + 1):
if j < v:
f[i][j] = f[i + 1][j]
else:
f[i][j] = f[i + 1][j] or f[i + 1][j - v]
if not f[0][target]:
return None
# 重构解
ans = []
j = target
for i in range(n):
v = a[i]
if j >= v and f[i + 1][j - v]:
ans.append(v)
j -= v
return ans
def special_palindrome(num: int) -> int:
s = str(num)
m = len(s)
mid = (m - 1) // 2
# 统计数字出现次数(只统计左半部分,包括中间)
cnt = [0] * 10
for d in s[:mid + 1]:
digit = int(d)
cnt[digit] += 2
def is_valid() -> bool:
"""验证是否满足特殊回文条件"""
for i, c in enumerate(cnt):
if c > 0 and c != i:
return False
return True
# 首先处理不修改高位的情况
if m % 2 == 0:
# 偶数长度
left_part = s[:mid + 1]
right_part = left_part[::-1]
pal = int(left_part + right_part)
if pal > num and is_valid():
return pal
else:
# 奇数长度,修改正中间
left_part = s[:mid]
mid_digit = int(s[mid])
cnt[mid_digit] -= 2 # 撤销中间数字的计数
for j in range(mid_digit, 10):
cnt[j] += 2
right_part = (left_part + str(j))[::-1]
pal = int(left_part + str(j) + right_part)
if pal > num and is_valid():
return pal
cnt[j] -= 2
def solve(i: int, j: int, mid_d: int) -> int:
"""解决子问题:在第i位填j,中间填mid_d"""
nonlocal cnt
# 备份计数
cnt_backup = cnt.copy()
# 处理中间位置
if mid_d > 0:
cnt[mid_d] += 1
# 计算可以自由填充的位置数
free = m // 2 - 1 - i
odd = 0
# 检查合法性并计算需要填充的数字
for k, c in enumerate(cnt):
if k < c: # 不合法情况
free = -1
break
if c > 0:
odd += k % 2
free -= (k - c) // 2
if free < 0 or odd > (m % 2):
# 恢复计数并返回-1
cnt = cnt_backup
return -1
# 构建需要填充的数字列表
a = []
for k in range(2, 10, 2): # 只考虑偶数
if cnt[k] == 0:
a.append(k // 2) # 左半需要填k/2个数
missing = zero_one_knapsack(a, free)
if missing is None:
cnt = cnt_backup
return -1
# 处理填充数字
for v in missing:
cnt[v * 2] = -v * 2 # 用负数表示可以自由填充
# 构建左半部分
t = list(s[:i])
t.append(str(j))
# 填充剩余数字
for k, c in enumerate(cnt):
if c > 0:
fill_count = k - c
else:
fill_count = -c
cnt[k] = 0 # 恢复
t.extend([str(k)] * (fill_count // 2))
# 构建完整回文数
left_str = ''.join(t)
right_str = left_str[::-1]
if mid_d > 0:
result_str = left_str + str(mid_d) + right_str
else:
result_str = left_str + right_str
# 恢复计数
cnt = cnt_backup
return int(result_str)
# 从右往左尝试修改高位数字
for i in range(m // 2 - 1, -1, -1):
digit = int(s[i])
cnt[digit] -= 2 # 撤销当前数字的计数
# 尝试增大当前位
for j in range(digit + 1, 10):
cnt[j] += 2
if m % 2 == 0:
ans = solve(i, j, 0)
if ans != -1:
return ans
else:
best_ans = math.inf
# 枚举中间位置填奇数
for d in range(1, 10, 2):
cnt[d] += 1
res = solve(i, j, d)
if res != -1:
best_ans = min(best_ans, res)
cnt[d] -= 1
if best_ans != math.inf:
return best_ans
cnt[j] -= 2
# 没找到,返回长度为m+1的最小回文数
return special_palindrome(10 ** m)
def main():
n = 2
result = special_palindrome(n)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <climits>
#include <cstring>
using namespace std;
// 从 a 中选一个字典序最小的、元素和等于 target 的子序列
// a 已经从小到大排序
// 无解返回空向量
vector<int> zeroOneKnapsack(const vector<int>& a, int target) {
int n = a.size();
vector<vector<bool>> f(n + 1, vector<bool>(target + 1, false));
f[n][0] = true;
// 倒着 DP
for (int i = n - 1; i >= 0; --i) {
int v = a[i];
for (int j = 0; j <= target; ++j) {
if (j < v) {
f[i][j] = f[i + 1][j];
} else {
f[i][j] = f[i + 1][j] || f[i + 1][j - v];
}
}
}
if (!f[0][target]) {
return {};
}
vector<int> ans;
int j = target;
for (int i = 0; i < n; ++i) {
int v = a[i];
if (j >= v && f[i + 1][j - v]) {
ans.push_back(v);
j -= v;
}
}
return ans;
}
long long specialPalindrome(long long num) {
string s = to_string(num);
int m = s.length();
int mid = (m - 1) / 2;
vector<int> cnt(10, 0);
for (int i = 0; i <= mid; ++i) {
cnt[s[i] - '0'] += 2;
}
auto isValid = [&]() -> bool {
for (int i = 0; i < 10; ++i) {
if (cnt[i] > 0 && cnt[i] != i) {
return false;
}
}
return true;
};
// 首先处理不修改高位的情况
if (m % 2 == 0) {
// 偶数长度
string left = s.substr(0, mid + 1);
string right = left;
reverse(right.begin(), right.end());
long long pal = stoll(left + right);
if (pal > num && isValid()) {
return pal;
}
} else {
// 奇数长度,修改正中间
string left = s.substr(0, mid);
int mid_digit = s[mid] - '0';
cnt[mid_digit] -= 2;
for (int j = mid_digit; j < 10; ++j) {
cnt[j] += 2;
string palindrome = left + to_string(j);
string rev = palindrome;
reverse(rev.begin(), rev.end());
palindrome = left + to_string(j) + rev;
long long pal = stoll(palindrome);
if (pal > num && isValid()) {
return pal;
}
cnt[j] -= 2;
}
}
// 解决子问题的函数
auto solve = [&](int i, int j, int mid_d) -> long long {
vector<int> cnt_backup = cnt;
// 处理中间位置
if (mid_d > 0) {
cnt[mid_d] += 1;
}
// 计算可以自由填充的位置数
int free = m / 2 - 1 - i;
int odd = 0;
// 检查合法性
for (int k = 0; k < 10; ++k) {
int c = cnt[k];
if (k < c) {
free = -1;
break;
}
if (c > 0) {
odd += k % 2;
free -= (k - c) / 2;
}
}
if (free < 0 || odd > (m % 2)) {
cnt = cnt_backup;
return -1;
}
// 构建需要填充的数字列表
vector<int> a;
for (int k = 2; k < 10; k += 2) {
if (cnt[k] == 0) {
a.push_back(k / 2);
}
}
vector<int> missing = zeroOneKnapsack(a, free);
if (missing.empty()) {
cnt = cnt_backup;
return -1;
}
// 处理填充数字
for (int v : missing) {
cnt[v * 2] = -v * 2;
}
// 构建左半部分
string left = s.substr(0, i);
left.push_back('0' + j);
// 填充剩余数字
for (int k = 0; k < 10; ++k) {
int c = cnt[k];
int fill_count;
if (c > 0) {
fill_count = k - c;
} else {
fill_count = -c;
cnt[k] = 0;
}
left.append(fill_count / 2, '0' + k);
}
// 构建完整回文数
string right = left;
reverse(right.begin(), right.end());
string result_str;
if (mid_d > 0) {
result_str = left + to_string(mid_d) + right;
} else {
result_str = left + right;
}
cnt = cnt_backup;
return stoll(result_str);
};
// 从右往左尝试修改高位数字
for (int i = m / 2 - 1; i >= 0; --i) {
int digit = s[i] - '0';
cnt[digit] -= 2;
// 尝试增大当前位
for (int j = digit + 1; j < 10; ++j) {
cnt[j] += 2;
if (m % 2 == 0) {
long long ans = solve(i, j, 0);
if (ans != -1) {
return ans;
}
} else {
long long best_ans = LLONG_MAX;
// 枚举中间位置填奇数
for (int d = 1; d < 10; d += 2) {
cnt[d] += 1;
long long res = solve(i, j, d);
if (res != -1) {
best_ans = min(best_ans, res);
}
cnt[d] -= 1;
}
if (best_ans != LLONG_MAX) {
return best_ans;
}
}
cnt[j] -= 2;
}
}
// 没找到,返回长度为 m+1 的最小回文数
long long next_pow = 1;
for (int i = 0; i < m; ++i) {
next_pow *= 10;
}
return specialPalindrome(next_pow);
}
int main() {
long long n = 2;
long long result = specialPalindrome(n);
cout << result << endl;
return 0;
}