当您想计算在两个限制之间具有某些给定属性的数字时,解决稍微简单的问题通常很有用
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 + 1
or 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+1
st数字小于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