根据对my answer here的评论,提出了问题(释义):
编写一个Python程序来查找一个4位数的整数,当它与自身相乘时,得到一个8位整数,其最后4位数等于原始数字。
我会发布我的答案,但我对更优雅的解决方案感兴趣简洁但易读的解决方案! (对于python来说,有人能够理解它吗?)
答案 0 :(得分:14)
这是一个没有任何模块的 1-liner 解决方案:
>>> next((x for x in range(1000, 10000) if str(x*x)[-4:] == str(x)), None)
9376
如果您考虑从1000
到3162
的数字,则它们的方块会为您提供7
位数字。因此,从3163
迭代将更加优化,因为正方形应该是8
数字。感谢@adrin这么好的一点。
>>> next((x for x in range(3163, 10000) if str(x*x)[-4:] == str(x)), None)
9376
答案 1 :(得分:6)
如果您对使用第三方库感到满意,可以使用numpy
。此版本与numba
结合使用以进行优化。
import numpy as np
from numba import jit
@jit(nopython=True)
def find_result():
for x in range(1e7**0.5, 1e9**0.5):
i = x**2
if i % 1e4 == x:
return (x, i)
print(find_result())
# (9376, 87909376)
答案 2 :(得分:5)
[差不多] 1-liner:
from math import sqrt, ceil, floor
print(next(x for x in range(ceil(sqrt(10 ** 7)), floor(sqrt(10 ** 8 - 1))) if x == (x * x) % 10000))
打印:
9376
定时:
%timeit next(x for x in range(ceil(sqrt(10 ** 7)), floor(sqrt(10 ** 8 - 1))) if x == (x * x) % 10000)
546 µs ± 32.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
@theausome的答案(最短(以字符为单位)):
%timeit next((x for x in range(3163, 10000) if str(x*x)[-4:] == str(x)), None)
3.09 ms ± 119 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
@jpp的答案(最快):
import numpy as np
from numba import jit
@jit(nopython=True)
def find_result():
for x in range(1e7**0.5, 1e9**0.5):
i = x**2
if i % 1e4 == x:
return (x, i)
%timeit find_result()
61.8 µs ± 1.46 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
答案 3 :(得分:5)
以下解决方案不像其他答案那样可读。但它缺乏可读性,效率提高。
蛮力方法检查给定范围内的每个数字,使其 O(10 ^ n)其中 n 是所需数字的数量(如果我们的帐户最差)用于乘法和转换)。
相反,我们可以从右到左构建所需的数字,只要生成的数字形成其方形的尾随数字,就可以添加数字。这提供了一个 O(n³)算法(参见底部的时间复杂度部分)。
def get_all_numbers(n, _digits=None):
# We are provided digits that form a number with desired properties
digits = [] if _digits is None else _digits
# Base case: if we attained the number of digits, return those digits
if len(digits) >= n:
return [int(''.join(digits))]
solutions = []
# Add digits from 0 to 9 and check if the new number satisfies our property
for x in range(10):
next_digits = [str(x)] + digits if x else ['0'] + digits
next_number = int(''.join(next_digits))
# If it does try adding yet another digit
if int(str(next_number ** 2)[-len(next_digits):]) == next_number:
solutions.extend(get_all_numbers(n, next_digits))
# Filter out solutions with too few digits
# Necessary as we could have prepended only zeros to an existing solution
return [sol for sol in solutions if sol >= 10 ** (n - 1)]
def get_numbers(n, sqr_length=None):
if sqr_length:
return [x for x in get_all_numbers(n) if len(str(x ** 2)) == sqr_length]
else:
return get_all_numbers(n)
get_numbers(4, 8) # [9376]
这对于少量数字来说不是必需的,但是允许解决更大输入的问题,其中蛮力解决方案需要永远。
get_numbers(100) # Outputs in a few milliseconds
对于给定数量的数字 n ,there can only exist at most two solutions而不是0和1.并且任何解决方案都是根据较少数字的解决方案构建的。
由此我们得出结论,尽管递归,但算法采用 O(n)步骤来寻找解决方案。
现在,每个步骤都必须执行一些乘法和转换。整数转换为O(n²),Python中的乘法使用的Karatsuba's algorithm小于转换。
总的来说,这会产生 O(n³)时间复杂度。
这可以通过使用线性整数转换算法来改进,然后提供 O(n ^(1 + log(3)))复杂度。
答案 4 :(得分:5)
以下是单行实施,不包括97.24%的候选人:
import itertools
step = itertools.cycle((24, 1, 24, 51))
[x for x in range(3176, 10000, next(step)) if str(x*x)[-4:] == str(x) ]
拨打号码abcd
。您可以通过限制最后两位cd
进行优化,只有4种合法可能性,不包括96%的cd候选者。类似地,我们只需要测试31< = ab< 100,不包括ab
候选人的31%。因此我们排除了97.24%
cd_cands = set((n**2) % 100 for n in range(0,99+1) if ((n**2 % 100) == n))
cd_cands = sorted(cd_cands)
[0, 1, 25, 76]
for ab in range(31,99+1):
for cd in cd_cands:
n = ab*100 + cd
if n**2 % 10**4 == n :
print("Solution: {}".format(n))
#break if you only want the lowest/unique solution
...
Solution: 9376
(当然你可以将其压缩成单行列表理解,但这会很难看)
现在我们可以用以下观察来分离多个for循环:严格来说,我们只需要在3162以上的第一个合法候选人开始测试,即3176.然后,我们通过连续添加步骤(100-76,1)来增加-0,25-1,76-25)=(24,1,24,51)
import itertools
step = itertools.cycle((24, 1, 24, 51))
abcd = 3176
while abcd < 10**4:
if abcd**2 % 10000 == abcd:
print("Solution: {}".format(abcd))
abcd += next(step)
再次可以减少到顶部显示的单线(/双线)。
答案 5 :(得分:4)
这是一个动态编程版本:
我们从右到左构建,使用知识为一个数字与自身平方,每个不太重要的数字也必须(在帖子上,这是@OlivierMelançon所采用的相同方法):
def dynamic(currents, tens, goal):
if tens == goal:
return [i for i in currents if len(str(i)) == tens]
else:
out = []
for x in currents:
for i in range(0,10):
val = x + i *10**tens
if val**2 % 10**(tens+1) == val:
out.append(val)
currents = out
tens +=1
return dynamic(currents, tens, goal)
我们称之为'当前目标',当前数十,目标数十:
dynamic([0],0,4)
#[9376]
在不到一秒的时间内很好地处理超大数字:
dynamic([0],0,100)
#[3953007319108169802938509890062166509580863811000557423423230896109004106619977392256259918212890625,6046992680891830197061490109937833490419136188999442576576769103890995893380022607743740081787109376]
答案 6 :(得分:3)
我想出的解决方案是:
# Loop over all 4 digit numbers
for x in range(1000, 10000):
# Multiply x*x
square = x*x
# Convert to a string
square = str(square)
# Check if the square is 8 digits long
if len(square) == 8:
# Check if the last 4 digets match x
if square.endswith(str(x)):
# print the number and it's square
print('x = {}\nx**2 = {}'.format(str(x), square))
哪个输出:
x = 9376
x**2 = 87909376
答案 7 :(得分:3)
简单的单行使用模运算符%
而不是字符串
print [x for x in range(3163, 10000) if x*x % 10000 == x]
# [9376]
范围3163
的低端是最小的四位数,其正方形是八位数。
答案 8 :(得分:2)
这里有一个班轮,只是
print(9376)
答案 9 :(得分:2)
我们只需要测试625个候选人中的1个。
解决方案A:
upper_limit = 10**4
lower_limit = int(10**3.5) + 1
rem = lower_limit % 625
if rem > 0:
lower_limit = lower_limit - rem + 625
for n in xrange(lower_limit, upper_limit, 625):
if n % 16 in [1, 15]:
print {1: n, 15: n+1}[n%16]
break
或解决方案B:
print (1 * (-39) * 16 + 0 * 1 * 625) % 10000
继续阅读以获得解释。
从测试所有候选人的蛮力列表理解开始:
from math import ceil
[n for n in xrange(ceil(10**3.5), 10000) if (n*n) % 10000 == n]
(ceil将10**7
的平方根舍入到最接近的整数。)
(请注意10000
是第一个数字,其正方形有9位数,并且循环不会测试该数字。)
...或者,如果您希望在找到解决方案后立即提前终止:
for n in xrange(ceil(10**3.5), 10000):
if (n*n) % 10000 == n:
print n
break
但请注意:我们正在寻找可被n**2 - n = n*(n-1)
整除的数字10**4 = 2**4 * 5**4
。
n
或n-1
是奇数;所以另一个必须被完整2**4 = 16
整除。同样,您n
和(n-1)
都不能被5
整除; n
或(n-1)
才能被5**4 = 625
整除。n
或(n-1)
)可被625
和16
整除,则该数字可被10000
整除。此类号码不能包含四位数字,因此n
或(n-1)
必须可被625
整除,另一个可被16
整除。625
的倍数;我们必须要小心记住625
的倍数可能是n
或(n-1)
;另一个必须被16
整除。所以:
upper_limit = 10**4
lower_limit = ceil(10**3.5)
rem = lower_limit % 625
if rem > 0:
lower_limit = lower_limit - rem + 625
for n in xrange(lower_limit, upper_limit, 625):
if (n-1) % 16 == 0:
print n
break
if (n+1) % 16 == 0:
print n+1
break
或者,如果您测试n而不是(n-1),并将两个条件分支合并到n % 16 in [1, 15]
,并且为了紧凑,您可以print {1: n, 15: n+1}[n%16]
。
这是解决方案A.(另外,如果您愿意,当然可以将n%16
替换为n & 0xf
。)
但等等!所有这一切实际上都可以使用...
完成我们希望找到n
,以便:
n = 0 (mod 625)
和n - 1 = 0 (mod 16)
,
要么:
n - 1 = 0 (mod 625)
和n = 0 (mod 16)
。
因此,在每种情况下,我们有两个方程式,互质模量,求解相同的数字n
:
n = 0 (mod 625)
和n = 1 (mod 16)
,
要不然
n = 1 (mod 625)
和n = 0 (mod 16)
。
现在(在这两种情况下)我们将使用扩展欧几里得算法来查找m1
和m2
,以便16*m1 + 625*m2 = 1
。事实证明,-39*16 + 1*625 = 1
导致上面的解决方案B,来自第二种情况。 (注意:第一种情况会产生625
,其正方形以0625
结尾,但不算作解决方案。)
为了完整性,这里是扩展欧几里德算法的实现。第二和第三个输出是m1
和m2
;在我们的案例中1
和-39
按某种顺序。
def extended_gcd(a, b):
last_remainder, remainder = abs(a), abs(b)
x, last_x, y, last_y = 0, 1, 1, 0
while remainder:
last_remainder, (quotient, remainder) = remainder, divmod(last_remainder, remainder)
x, last_x = last_x - quotient*x, x
y, last_y = last_y - quotient*y, y
return last_remainder, last_x * ((-1)**(a < 0)), last_y * ((-1)**(b < 0))