簡體   English   中英

Python:找到連續正方形之和的回文數的有效方法

[英]Python: efficient way to find palindrome numbers that are sums of consecutive squares

這是代碼戰爭中的一個問題。

給定一個整數N,寫一個函數 ,找出區間(1,...,N)中有多少個數是回文並且可以表示為連續方格的總和。

例如, 值(100)將返回3.實際上,具有上述屬性的小於100的數字是:

  • 5 = 1 + 4,
  • 55 = 1 + 4 + 9 + 16 + 25
  • 77 = 16 + 25 + 36

像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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM