当您想计算在两个限制之间具有某些给定属性的数字时,解决稍微简单的问题通常很有用
0和之间有多少具有给定属性的数字n?
固定一个限制可以使问题更容易解决。当较简单的问题解决后,您可以通过简单的减法得到原始问题的解决方案:
countBetween(a,b) = countTo(b) - countTo(a)
或者countTo(b ± 1) - countTo(a ± 1),取决于是否包含限制countTo以及应包含哪些限制countBetween。
如果可能出现负极限(我认为不是回文),countTo(n)则应该是<= 0负极限n(可以将函数视为相对于计数度量的积分)。
所以让我们确定
palindromes_below(n) = #{ k : 0 <= k < n, k is a palindrome }
如果我们假设 0 不是回文,我们会在第一部分得到更统一的公式,所以对于第一部分,我们这样做。
第 1 部分:有多少个给定位数d的回文?
第一个数字不能是0,否则它是不受限制的,因此有 9 种可能的选择(b-1对于任意基数的回文b)。
最后一个数字与第一个数字相同,因为它应该是回文。
第二个数字 - if d >= 3- 可以独立于第一个数字任意选择。这也决定了倒数第二个数字。
如果d >= 5,也可以自由选择第三位,以此类推。
片刻的思考表明,对于d = 2*k + 1or d = 2*k + 2,有一些k数字可以不受限制地选择,而一个数字(第一个)受到限制,即它是非零的。所以有
9 * 10**k
d-digit 回文然后((b-1) * b**k对于 base b)。
这是一个很好且简单的公式。由此,使用几何和的公式,我们可以很容易地获得小于 10 n的回文数(即最多n位数):
(对于一般基数b,数字分别2 * (b**(n/2) - 1)为(b+1) * b**((n-1)/2) - 2)。
这不再那么统一了,但仍然很简单:
def palindromes_up_to_n_digits(n):
if n < 1:
return 0
if n % 2 == 0:
return 2*10**(n//2) - 2
else:
return 11*10**(n//2) - 2
(请记住,我们还不算 0)。
现在剩下的部分。n > 0给定k数字,回文< n要么是
- 少于
k数字的回文,有palindromes_up_to_n_digits(k-1)它们,或
k正好数字小于 的回文数n。
所以还是要算后者。
第2部分:
让m = (k-1)//2和
d[1] d[2] ... d[m] d[m+1] ... d[k]
的十进制表示n(整个事情对其他基础的工作原理相同,但我没有在下面明确提及),所以
k
n = ∑ d[j]*10**(k-j)
j=1
对于每一个1 <= c[1] < d[1],我们可以自由选择m数字c[2], ..., c[m+1]来获得一个回文。
p = c[1] c[2] ... c[m+1] {c[m+1]} c[m] ... c[2] c[1]
(c[m+1]奇数出现一次,偶数出现k两次k)。现在,
c[1]*(10**(k-1) + 1) <= p < (c[1] + 1)*10**(k-1) <= d[1]*10**(k-1) <= n,
所以所有这些10**m回文(对于给定的选择c[1]!)都小于n.
因此存在(d[1] - 1) * 10**m k-digit 回文数,其第一个数字小于 的第一个数字n。
现在让我们考虑第一个数字小于的k-digit 回文串。d[1]n
如果k == 2,则只有一个 if d[1] < d[2],否则没有。如果k >= 3,对于每个0 <= c[2] < d[2],我们可以自由选择m-1数字c[3] ... c[m+1]来获得回文
p = d[1] c[2] c[3] ... c[m] c[m+1] {c[m+1]} c[m] ... c[3] c[2] d[1]
我们看到p < n:
d[1]*(10**(k-1) + 1) + c[2]*(10**(k-2) + 10)
<= p < d[1]*(10**(k-1) + 1) + (c[2] + 1)*(10**(k-2) + 10)
<= d[1]*(10**(k-1) + 1) + d[2]*(10**(k-2) + 10) <= n
(假设k > 3,k == 3替换10**(k-2) + 10为 10)。
所以这使得d[2]*10**(m-1) k-digit 回文第一个数字d[1]和第二个数字小于d[2]。
继续,对于1 <= r <= m,有
d[m+1]*10**(m-r)
k-digit 回文数,其第一个r数字是d[1] ... d[r]并且其r+1st数字小于d[r+1]。
总结一下,有
(d[1]-1])*10**m + d[2]*10**(m-1) + ... + d[m]*10 + d[m+1]
k- 数字回文,其前一位m+1数字小于 的对应数字,n所有前面的数字都等于 的对应数字n。显然,这些都小于n。
有一位数的k回文数p,其第一个m+1数字是d[1] .. d[m+1],我们也必须计算它 if p < n。
所以,总结起来,现在也加入 0,我们得到
def palindromes_below(n):
if n < 1:
return 0
if n < 10:
return n # 0, 1, ..., n-1
# General case
dec = str(n)
digits = len(dec)
count = palindromes_up_to_n_digits(digits-1) + 1 # + 1 for 0
half_length = (digits-1) // 2
front_part = dec[0:half_length + 1]
count += int(front_part) - 10**half_length
i, j = half_length, half_length+1
if digits % 2 == 1:
i -= 1
while i >= 0 and dec[i] == dec[j]:
i -= 1
j += 1
if i >= 0 and dec[i] < dec[j]:
count += 1
return count
由于限制都包含在给定问题的计数中(除非 OP 被误解),所以我们有
def count_palindromes(start, end):
return palindromes_below(end+1) - palindromes_below(start)
快速解决方案:
>>> bench(10**100,10**101-1)
900000000000000000000000000000000000000000000000000 palindromes between
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
and
99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
in 0.000186920166016 seconds