[英]Python: efficient way to find palindrome numbers that are sums of consecutive squares
这是代码战争中的一个问题。
给定一个整数N,写一个函数值 ,找出区间(1,...,N)中有多少个数是回文并且可以表示为连续方格的总和。
例如, 值(100)将返回3.实际上,具有上述属性的小于100的数字是:
像4,9,121等完美正方形的回文不算数。 测试最多N = 10 ^ 7。
我可以解决问题并通过所有测试用例,但我无法满足效率要求(该过程在12秒后被杀死)。
我只是错过了一些关键的观察,或者下面的代码效率有问题吗? 为了更好的可读性,我把它分解了一下。
from numpy import cumsum
def is_palindrome(n):
return str(n) == str(n)[::-1]
def values(n):
limit = int(n**0.5) # could be improved
pals = []
for i in range(1,limit):
# take numbers in the cumsum that are palindromes < n
sums = [p for p in cumsum([i**2 for i in range(i,limit)]) if p<n and is_palindrome(p)]
# remove perfect-squares and those already counted
pals.extend(k for k in sums if not (k in pals) and not (k**0.5).is_integer())
return len(pals)
注意:我知道使用sqrt(n).is_integer()检查数字是一个完美的正方形可能并不完美,但对于这种情况应该足够了。
除了提高计算效率外,您还可以改进算法策略。 有一个所有平方和的公式s(n)=1²+2²+ ... +n²= n(n + 1)(2n + 1)/ 6。 因此,您可以计算s(n)-s(m-1),而不是添加m²+(m + 1)²+ ... +n²。 所有s(n)的列表都可以使用itertools
查找所有可能的对并减去它们,这样可以加快程序的运行速度。
在@nm建议之后,您可以预先计算n<=10e7
所有值,并返回匹配数:
import bisect
def value(n):
vals = [5, 55, 77, 181, 313, 434, 505, 545, 595, 636, 818, 1001, 1111, 1441, 1771, 4334, 6446, 17371, 17871, 19691, 21712, 41214, 42924, 44444, 46564, 51015, 65756, 81818, 97679, 99199, 108801, 127721, 137731, 138831, 139931, 148841, 161161, 166661, 171171, 188881, 191191, 363363, 435534, 444444, 485584, 494494, 525525, 554455, 629926, 635536, 646646, 656656, 904409, 923329, 944449, 964469, 972279, 981189, 982289, 1077701, 1224221, 1365631, 1681861, 1690961, 1949491, 1972791, 1992991, 2176712, 2904092, 3015103, 3162613, 3187813, 3242423, 3628263, 4211124, 4338334, 4424244, 4776774, 5090905, 5258525, 5276725, 5367635, 5479745, 5536355, 5588855, 5603065, 5718175, 5824285, 6106016, 6277726, 6523256, 6546456, 6780876, 6831386, 6843486, 6844486, 7355537, 8424248, 9051509, 9072709, 9105019, 9313139, 9334339, 9343439, 9435349, 9563659, 9793979, 9814189, 9838389, 9940499, 10711701, 11122111, 11600611, 11922911, 12888821, 13922931, 15822851, 16399361, 16755761, 16955961, 17488471, 18244281, 18422481, 18699681, 26744762, 32344323, 32611623, 34277243, 37533573, 40211204, 41577514, 43699634, 44366344, 45555554, 45755754, 46433464, 47622674, 49066094, 50244205, 51488415, 52155125, 52344325, 52722725, 53166135, 53211235, 53933935, 55344355, 56722765, 56800865, 57488475, 58366385, 62988926, 63844836, 63866836, 64633646, 66999966, 67233276, 68688686, 69388396, 69722796, 69933996, 72299227, 92800829, 95177159, 95544559, 97299279]
return bisect.bisect_right(vals, n)
你进入了几百纳秒的境界......
代码中的一些错误:
1 - 您需要使用p <= n
而不是p < n
(尝试n = 77
)
2 - 使用set而不是list来存储您的答案,这将加快您的解决方案。
3 - 你不需要在[i, limit)
范围内调用cumsum
,你可以累积直到总和大于n
。 这也将加速您的解决方案。
4 - 尽量减少“pythonic”。 不要填写你的冗长和丑陋的列表理解代码。 (这不是一个错误)
这是您进行一些更改后的代码:
def is_palindrome(s):
return s == s[::-1]
def values(n):
squares = [0]
i = 1
while i * i <= n:
squares.append(squares[-1] + i * i)
i += 1
pals = set()
for i in range(1, len(squares)):
j = i + 1
while j < len(squares) and (squares[j] - squares[i - 1]) <= n:
s = squares[j] - squares[i - 1]
if is_palindrome(str(s)):
pals.add(s)
j += 1
return len(pals)
你可以使用一些itertools
技巧来加快这一点。
import itertools
limit = int(n**0.5) # as before, this can be improved but non-obviously.
squares = [i**2 for i in range(1, limit+1)]
num_squares = len(squares) # inline this to save on lookups
seqs = [(squares[i:j] for j in range(i+2, num_squares)) for i in range(num_squares-2)]
seqs现在是构建方形序列的生成器列表。 例如,对于n=100
我们有:
[ [[1, 4], [1, 4, 9], [1, 4, 9, 16], [1, 4, 9, 16, 25], [1, 4, 9, 16, 25, 36], [1, 4, 9, 16, 25, 36, 49], [1, 4, 9, 16, 25, 36, 49, 64], [1, 4, 9, 16, 25, 36, 49, 64, 81]],
[[4, 9], [4, 9, 16], [4, 9, 16, 25], [4, 9, 16, 25, 36], [4, 9, 16, 25, 36, 49], [4, 9, 16, 25, 36, 49, 64], [4, 9, 16, 25, 36, 49, 64, 81]],
[[9, 16], [9, 16, 25], [9, 16, 25, 36], [9, 16, 25, 36, 49], [9, 16, 25, 36, 49, 64], [9, 16, 25, 36, 49, 64, 81]],
[[16, 25], [16, 25, 36], [16, 25, 36, 49], [16, 25, 36, 49, 64], [16, 25, 36, 49, 64, 81]],
[[25, 36], [25, 36, 49], [25, 36, 49, 64], [25, 36, 49, 64, 81]],
[[36, 49], [36, 49, 64], [36, 49, 64, 81]],
[[49, 64], [49, 64, 81]],
[[64, 81]],
]
如果我们sum
那些我们可以使用itertools.takewhile
来减少我们稍后需要做的相等检查的数量:
sums = [itertools.takewhile(lambda s: s <= n, lst) for lst in [map(sum, lst) for lst in seqs]]
这大大减少了结果列表,同时计算累积的总和
[ [5, 14, 30, 55, 91],
[13, 29, 54, 90],
[25, 50, 86],
[41, 77],
[61],
[85],
[],
[],
]
我们可以使用filter(None, sums)
删除那些空列表,然后与itertools.chain.from_iterable
并传入is_palindrome
。
def is_palindrome(number):
s = str(number)
return s == s[::-1]
result = [k for k in itertools.chain.from_iterable(filter(None, sums)) if is_palindrome(k)]
我们也可以在这里进行完美的方形检查,但我们已经知道任何完美的方形都必须在squares
。 对于任意大的n
,将这两者组合成集合并使用set.difference
变得更便宜和更便宜。
result = {k for k in ...} # otherwise the same, just use curly braces
# to invoke set comprehension instead of list
squareset = set(squares)
final = result.difference(squareset)
# equivalent to `result - squareset`
很多这些网站使用的是以编程方式相对容易解决的问题,但很难有效地完成。 这将是您遇到的常见问题。
至于解决方案,首先尝试提出一种有效的算法。 然后,如果它不满足时间约束,那么就可以实现在计算上花费较少的python标准库方法来实现相同的功能。 例如,pals.extend()每次遍历整个列表,还是指向最后一个元素? 如果它遍历,那么寻找一个没有的方法(pals.append()可能会这样做,但我不确定
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.