找到一个4位数字,其正方形是8位数,最后4位数是原始数字

时间:2018-04-05 12:59:04

标签: python math integer-arithmetic

根据对my answer here的评论,提出了问题(释义):

编写一个Python程序来查找一个4位数的整数,当它与自身相乘时,得到一个8位整数,其最后4位数等于原始数字。

我会发布我的答案,但我对更优雅的解决方案感兴趣简洁但易读的解决方案! (对于python来说,有人能够理解它吗?)

10 个答案:

答案 0 :(得分:14)

这是一个没有任何模块的 1-liner 解决方案:

>>> next((x for x in range(1000, 10000) if str(x*x)[-4:] == str(x)), None)
9376

如果您考虑从10003162的数字,则它们的方块会为您提供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

  • nn-1是奇数;所以另一个必须被完整2**4 = 16整除。同样,您n(n-1)都不能被5整除;
  • 所以我们需要n(n-1)才能被5**4 = 625整除。
  • 如果其中任何一个(n(n-1))可被62516整除,则该数字可被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。)

但等等!所有这一切实际上都可以使用...

完成

Chinese Remainder Theorem

我们希望找到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)

现在(在这两种情况下)我们将使用扩展欧几里得算法来查找m1m2,以便16*m1 + 625*m2 = 1。事实证明,-39*16 + 1*625 = 1导致上面的解决方案B,来自第二种情况。 (注意:第一种情况会产生625,其正方形以0625结尾,但不算作解决方案。)

为了完整性,这里是扩展欧几里德算法的实现。第二和第三个输出是m1m2;在我们的案例中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))