从我的答案评论中,问题被问到(转述):
编写一个Python程序,以找到一个4位整数,当它与自己相乘时,得到一个8位整数,其最后4位数字与原始数字相等。
我会发布我的答案,但我更感兴趣的是更加简洁但易于阅读的解决方案! (新手能理解吗?)
从我的答案评论中,问题被问到(转述):
编写一个Python程序,以找到一个4位整数,当它与自己相乘时,得到一个8位整数,其最后4位数字与原始数字相等。
我会发布我的答案,但我更感兴趣的是更加简洁但易于阅读的解决方案! (新手能理解吗?)
这是一个不需要任何模块的一行代码解决方案:
>>> 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
3163
是10000000
(最小的8位数)的平方根,这个数字并非凭空捏造。 - Aaron N. Brocknumpy
。这个版本与 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)
[近乎]一行代码:
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)
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)
math
应该是语言的一部分。此外,我没有考虑使用 sqrt
函数,在我的答案中,我正在无用地测试平方不是 8 位数字的答案。 - Aaron N. Brockstring
转换吗? - Aaron N. Brockstr
是一项,而从1000开始,而不是3163则是另一项。 - adrinprint(next(x for x in range(__import__('math').ceil(__import__('math').sqrt(10 ** 7)), __import__('math').floor(__import__('math').sqrt(10 ** 8 - 1))) if x == (x * x) % 10000))
- Gabrieldef 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,除了0和1外,最多只有两个解。而任何解都是由较小数字位数的解构建而来。
因此,尽管存在递归,该算法需要O(n)步才能找到解决方案。
现在,每一步都必须执行一些乘法和转换。整数转换的O(n²),Python中的乘法使用Karatsuba算法,其小于转换。
总体而言,这产生了一个O(n³)的时间复杂度。
通过使用线性整数转换算法可以改进这一点,然后提供O(n^(1 + log(3)))的复杂度。
n
,最多有 2 种解决方案。我们正在寻找 k^2 = k (mod 10^n)
或等价的 k*(k-1) = 0 (mod 10^n)
的解决方案。这意味着要么 k
是 2^n
的倍数且 k-1
是 5^n
的倍数,要么反之亦然。根据中国剩余定理,这足以确定 k
的值为 2 种可能性之一,其中一种可能会因前导零而被消除。 - user2357112k
是一个候选项,另一个就是 (1 - k) % 10**n
。 - user2357112import 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,排除了31%的ab
候选者。因此,我们排除了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)
并且这可以被简化为顶部显示的一行(或两行)代码。
更新:正如@mathmandan展示的那样,我们可以进一步改进step = itertools.cycle((1, 624))
,消除99.68%的候选项。 并从3750开始搜索。
step = itertools.cycle((1, 624))
,消除了99.68%的候选项。并从625 * 6 = 3750开始搜索。 - smcidef 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]
使用取模运算符%
而不是字符串的简单一行代码
print [x for x in range(3163, 10000) if x*x % 10000 == x]
# [9376]
范围的下限3163
是最小的四位数,其平方是八位数。
# 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
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
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]
for n in xrange(ceil(10**3.5), 10000):
if (n*n) % 10000 == n:
print n
break
10**4 = 2**4 * 5**4
整除的数字 n**2 - n = n*(n-1)
。
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
的倍数;我们只需要注意可能是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 & 0xf
代替n%16
。)
但等等!实际上,所有这些都可以用...
我们想要找到一个n
,使得:
n = 0 (mod 625)
并且 n - 1 = 0 (mod 16)
,n - 1 = 0 (mod 625)
并且 n = 0 (mod 16)
。因此,在每种情况下,我们有两个具有互质模数的方程式,解决同一个数字n
:
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))
from math import ceil, sqrt; ... ceil(10**3.5)
- smcistep = itertools.cycle((1, 624))
,并且从625 * 6 = 3750开始搜索。 - smci这里只有一句话,就是:
print(9376)
sys.stdout.buffer.raw.write(b'9376\n')
来减少几微秒的时间。 :P - abarnert