[英]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.