2026-01-15:下一个特殊回文数。用go语言,给定一个整数 n,求出一个比 n 更大的最小整数,该整数需要满足两条规则: 1. 它的十进制表示从左到右与从

16 阅读12分钟

2026-01-15:下一个特殊回文数。用go语言,给定一个整数 n,求出一个比 n 更大的最小整数,该整数需要满足两条规则:

  1. 它的十进制表示从左到右与从右到左完全一致(即读起来是对称的)。

  2. 对于每一个数字 k(0–9),如果该数字出现在这个数中,那么它恰好出现 k 次;不出现的数字则出现次数为 0。

返回满足这两条条件且严格大于 n 的最小整数。

0 <= n <= 1000000000000000。

输入: n = 2。

输出: 22。

解释:

22 是大于 2 的最小特殊数,因为它是一个回文数,并且数字 2 恰好出现了 2 次。

题目来自力扣3646。

解决步骤分析

  1. 初始化与预处理

    • 将输入的整数 n 转换为字符串 s,以便于逐位处理。
    • 计算字符串的长度 m,并确定回文数的“中间位置” mid,其值为 (m - 1) / 2。这对于后续生成回文数至关重要。
    • 初始化一个长度为10的计数器数组 cnt,用于记录当前考虑的数字组合中,每个数字(0-9)计划出现的次数。初始时,计数器会根据当前数字的前半部分进行设置。
  2. 检查当前数字本身

    • 首先,程序会尝试基于 n 本身构造一个回文数。这是通过取 n 的前半部分(包括中间数字,如果长度是奇数的话),然后将其反转并拼接在后面,从而形成一个完整的回文数 pal
    • 接着,程序会验证这个回文数 pal 是否大于原始的 n,并且其数字出现次数是否符合规则(通过 valid 函数检查计数器 cnt)。如果符合,则直接返回 pal 作为结果。
    • 如果 n 的长度是奇数,还会特别尝试增大中间位的数字,看看是否能得到符合条件的更大回文数。
  3. 寻找更大的候选数(核心逻辑)

    • 如果基于 n 的当前结构找不到更大的特殊回文数,程序会从右向左依次尝试增大回文数左半部分的数字。这是一个贪心策略的体现,旨在用尽可能小的增量找到一个更大的数。
    • 关键函数 solve:对于在位置 i 尝试填入一个更大的数字 j 的情况,这个函数负责构造出字典序最小的可能回文数。
      • 可行性检查:它首先检查在剩余的固定数字位确定后,需要“自由填充”的位数是多少。同时,它会验证当前的数字出现次数规则是否可能被满足(例如,每个数字k的计划出现次数不能超过k,所有数字的总出现次数必须等于回文数长度等)。如果不可能,则放弃此分支。
      • 背包问题求解:为了以最小的字典序填充剩余的自由位,该问题被转化为一个动态规划(0-1背包)问题。目标是选择一组数字(每个数字代表需要成对出现,因为回文数是对称的),使得它们的“代价”之和正好等于需要自由填充的位数。这里追求的是字典序最小解。
      • 构造回文数:根据背包问题求解出的数字组合,以及必须出现的数字,程序会构造出回文数左半部分(包括中间位),然后通过镜像对称生成完整的回文数字符串,并转换为整数。
  4. 递增数位长度

    • 如果即使在当前数字长度 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;
}

在这里插入图片描述