这是来自codewars的问题。
给定一个整数N,写一个函数 values ,找出区间(1,...,N)中有多少个数是回文,并且可以表示为连续方格的总和。
例如, values(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()检查一个数字是一个完美的正方形可能并不完美,但对于这种情况应该足够了。
答案 0 :(得分:4)
除了提高计算效率外,您还可以改进算法策略。有一个所有平方和的公式s(n)=1²+2²+ ... +n²= n(n + 1)(2n + 1)/ 6。因此,您可以计算s(n)-s(m-1),而不是添加m²+(m + 1)²+ ... +n²。所有s(n)的列表都可以找到itertools
所有可能的对并减去它们,这样可以加速你的程序。
答案 1 :(得分:3)
按照@n.m建议,您可以预先计算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)
你进入了几百纳秒的境界......
答案 2 :(得分:2)
代码中的一些错误:
1 - 您需要使用p <= n
代替p < n
(尝试n = 77
)
2 - 使用设置代替列表来存储您的答案,这将加快您的解决方案。
3 - 您不需要在cumsum
范围内呼叫[i, limit)
,您可以累积,直到总和大于n
。这也将加快您的解决方案。
4 - 尽量减少&#34; pythonic&#34;。不要填写你的冗长和丑陋的列表理解代码。 (这不是一个错误)
这是经过一些更改之后的代码:
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)
答案 3 :(得分:2)
你可以使用一些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`
答案 4 :(得分:-2)
许多这些网站使用的是以编程方式相对容易解决的问题,但很难有效地完成。这将是您遇到的常见问题。
至于解决方案,首先尝试提出一种有效的算法。然后,如果它不满足时间约束,那么就可以实现找到计算量较少的python标准库方法来实现同样的事情。例如,pals.extend()每次遍历整个列表,还是指向最后一个元素?如果它遍历,那么寻找一个没有的方法(pals.append()可能会这样做,但我不确定