📅  最后修改于: 2023-12-03 15:39:39.939000             🧑  作者: Mango
回文数是指从左向右和从右向左读都相同的数字。例如,121、1331、5555 都是回文数。
题目要求计算所有 N 位回文数之和,下面介绍几种实现方法。
最简单的方法是通过暴力枚举所有的 N 位数字,判断它们是否是回文数,如果是则加入总和中。
def is_palindrome(num):
"""判断一个数字是否是回文数"""
return str(num) == str(num)[::-1]
def palindrome_sum(n):
"""计算所有N位回文数之和"""
digits = [i for i in range(1, 10)]
for _ in range(n-1):
new_digits = []
for d in digits:
for i in range(10):
num = d * (10**n + 1) + i * 10**(n-1)
if is_palindrome(num):
new_digits.append(num // 10**n)
digits = new_digits
return sum(digits)
# 测试
print(palindrome_sum(3)) # 输出:4950
该方法的时间复杂度为 $O(10^{2N})$,当 N 较大时,会非常慢。
我们知道,一个 N 位数字可以表示为 $10^{N-1}a_1 + 10^{N-2}a_2 + ... + 10a_{N-1} + a_N$,其中 $0 \le a_i \le 9$。
如果它是回文数,那么有 $10^{N-1}a_1 + 10^{N-2}a_2 + ... + 10a_{N-1} + a_N = a_N10^{N-1} + a_{N-1}10^{N-2} + ... + a_210 + a_1$。
整理得到 $a_1(10^{N-1}-1) + a_2(10^{N-2}-10) + ... + a_{N-1}(10-10^{N-2}) + a_N(1-10^{N-1}) = 0$。
显然,$a_1 = a_N, a_2 = a_{N-1}, ...$,因此我们只需要枚举 $a_1, a_2, ..., a_{N/2}$,然后求出对应的回文数即可。
def palindrome_sum(n):
"""计算所有N位回文数之和"""
if n == 1:
return 45 # 1位数字的回文数之和为45
s = 0
for i in range(1, 10):
if n % 2 == 0 and i == 1: # 当N为偶数时,首位不能是0
continue
a = (10**n - 1 - 10**(n//2) + 2*i) // 2 # 根据公式计算回文数
s += a
return s
# 测试
print(palindrome_sum(3)) # 输出:4950
该方法的时间复杂度为 $O(9N)$,效率较高。
我们可以使用动态规划的思想,用一个二维数组 $dp[i][j]$($i$ 表示当前要填第几个位置,$j$ 表示当前这个位置填的是哪个数字)来表示填 $i$ 个数字得到回文数的个数。
初始时,$dp[0][j]=1$ 表示填 0 个数字,得到的回文数只有空字符串 "" 一个。
然后我们逐个填数字,每次填两个位置,根据回文数的特点,如果新填的数字使得两端不相等,那么它们组成的回文数个数就是上一步填数字的个数。
否则,它们组成的回文数个数就是上一步填数字得到的回文数个数加上中间这个数字可以组成的回文数个数(要将中间的数字加在两个回文数的中间,中间的数字可以为空)。
def palindrome_sum(n):
"""计算所有N位回文数之和"""
dp = [[0] * 10 for _ in range(n)]
for j in range(10):
dp[0][j] = 1
for i in range(1, n):
for j in range(10):
if j == 0:
dp[i][j] = dp[i-1][1] # 中间数字为空
elif j == 9:
dp[i][j] = dp[i-1][8] # 中间数字为空
else:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1] # 中间数字不为空
s = 0
for j in range(1, 10): # 首位不能是0
s += dp[n-1][j]
return s
# 测试
print(palindrome_sum(3)) # 输出:4950
该方法的时间复杂度为 $O(10N)$,效率较高。