【问题标题】:Code takes a lot of time to run for large numbers运行大量代码需要大量时间
【发布时间】:2019-03-03 21:49:53
【问题描述】:

给定棋盘中的方格数(例如拼字游戏或国际象棋棋盘)、N 和尺寸 AxB,此代码尝试确定所有可能的 可以在棋盘中给出 N 个方格的尺寸组合。

示例:N = 8

有四种可能的尺寸组合可以在棋盘中准确地获得 8 个方格。因此,代码输出板尺寸 1x8 2x3、3x2 和 8x1。 8x1 板有八个 1x1 方格; 3x2 棋盘有六个 1x1 方格和两个 2x2 方格。

这是我的解决方案:

def dims(num_sqrs):
    dim_list=[]
    for i in range(1,num_sqrs+1):
        temp = []
        for x in range(1,num_sqrs+1):
            res = 0
            a = i
            b = x
            while (a != 0) and (b !=0):
                res = res + (a*b)
                a = a -1
                b = b-1
            if res == num_sqrs:
                dim_list.append((i,x))
    print(dim_list)

dims(8)

但是,对于较大的 N 值,此代码需要花费太多时间来运行。 任何优化代码效率的建议将不胜感激。

【问题讨论】:

  • 2x3 和 3x2 8 个正方形是怎样的?那不是每个6个正方形吗?简单的算术?
  • 什么,您是否也在计算 2x2“正方形”而不仅仅是瓷砖?你应该澄清你的问题。
  • @Qudus:这不是“故障”,而是您的算法效率低下。我无法将您的算法或输出表单映射到您链接的问题。此外,在 Stack Overflow 上通常不允许 链接 到主要问题描述。
  • @Qudus:同样,请不要链接到您的材料的外部描述。记住 Stack Overflow 的目的;一个直接的结果是你的问题描述应该是独立的。其次,您避免回答我们对您的困难的具体担忧。您发布的代码以与您链接的网站不同的格式生成输出,并且具有我们无法解决问题的计算。我对给定问题的解决方案是一行;看来您正在尝试解决其他问题。
  • @Prune 我链接的网站是为了让您了解问题所在。你有一个 2x3 板。只计算棋盘中的 1x1 方格显然会得到 6。如果你计算内部的 2x2 方格。那么我们总共有8个正方形。这不仅仅是一个 2x3 的棋盘,它会给你一个棋盘中的 8 个方格。 8 x 1、1 x 8、3 x 2 也会给出 8 个正方形。因此,您将给定数量的棋盘中的方格总数传递给该函数,它会为您提供所有可能的组合 (m x n),这些组合将为您提供该数量的方格。

标签: python algorithm


【解决方案1】:

这里有两个非常明显的观察结果:

  1. AxB 的平方数与 BxA 的平方数相同

  2. 如果 C>B,则 AxC 的平方数大于 AxB 的平方数

鉴于这些事实,应该清楚的是:

  1. 我们只需要考虑 A≤B 的 AxB,因为如果 A≠B 我们可以将 BxA 添加到列表中

  2. 对于给定的 A 和 N,最多有一个 B 值的平方数为 N。

下面的代码是基于上面的。它依次尝试每个 AxA,每个 AxA 检查是否有一些 B≥A 产生正确的平方计数。当 AxA 的平方数超过 N 时停止。

现在,要找到 B 的正确值,需要进行一些不太明显的观察。

  1. 假设 AxA 的平方数为 N。那么 (A+1)x(Ax1) 的平方数为 N + (A+1)²。

    证明:AxA 中的每个正方形都可以通过其左上坐标 [i, j] 及其大小s 来识别。我会把它写成 [s: *i, j]。 (这里我假设坐标是从零开始的,从上到下,从左到右。)

    对于每个这样的正方形 0 ≤ i + s j + s

    现在,假设我们将每个正方形 [s: i, j] 更改为基于相同坐标但具有大小的正方形一个更大的,[s+1: i, j]。这个新正方形是 (A+1)x(A+1) 中的正方形,因为 0 ≤ i + s + 1 j)。所以这个变换给了我们 A + 1 中每个大小至少为 2 的方格。我们唯一遗漏的方格是大小为 1 的方格,它们正好有 (A+1)×(A+1) 个.

  2. 假设 AxB 的平方数为 N,并且 B≥A。那么 Ax(B+1) 的平方数是 N + 从 1 到 A 的每个整数之和。(这些是三角形数,即 A×(A+1)/2;我认为这是众所周知的。 )

    证明:Ax(B+1) 中的正方形恰好是 AxB 中的正方形加上右侧包含 Ax(B+1) 的最后一列的正方形。所以我们只需要计算这些。有一个大小为 A 的正方形,两个大小为 A-1 的正方形,三个大小为 A-2 的正方形,依此类推,直到 A 大小为 1 的正方形。

所以对于给定的 A,我们可以计算 AxA 的平方计数以及 B 每次增加时平方计数的增量。如果增量甚至除以目标计数和 AxA 计数之间的差,那么我们'找到了 AxB。

下面的程序还依赖于另一个代数恒等式,这非常简单:两个连续的三角形数之和是一个平方。通过排列两个三角形就很明显了。较大的包含正方形的对角线。这些事实用于计算 A 的下一个基值和增量。

def finds(n):
  a = 1
  base = 1   # Square count for AxA
  inc = 1    # Difference between count(AxB) and count(AxB+1)
  rects = []
  while base < n:
    if (n - base) % inc == 0:
      rects.append((a, a + (n - base) // inc))
    a += 1
    newinc = inc + a
    base += inc + newinc
    inc = newinc
  if base == n:
    return rects + [(a, a)] + list(map(lambda p:p[::-1], reversed(rects)))
  else:
    return rects + list(map(lambda p:p[::-1], reversed(rects)))

该函数最慢的部分是在最后添加 AxB 解决方案的倒数,我这样做只是为了简化正确计算解决方案的过程。我的第一次尝试几乎快了两倍,使用循环while base &lt;= n,然后返回rects。但它仍然足够快。

例如:

>>> finds(1000000)
[(1, 1000000), (4, 100001), (5, 66668), (15, 8338), (24, 3341),
 (3341, 24), (8338, 15), (66668, 5), (100001, 4), (1000000, 1)]

>>> finds(760760)
[(1, 760760), (2, 253587), (3, 126794), (4, 76077), (7, 27172),
 (10, 13835), (11, 11530), (12, 9757), (13, 8364), (19, 4010),
 (20, 3629), (21, 3300), (38, 1039), (39, 988), (55, 512),
 (56, 495), (65, 376), (76, 285), (285, 76), (376, 65),
 (495, 56), (512, 55), (988, 39), (1039, 38), (3300, 21),
 (3629, 20), (4010, 19), (8364, 13), (9757, 12), (11530, 11),
 (13835, 10), (27172, 7), (76077, 4), (126794, 3), (253587, 2), 
 (760760, 1)]

The last one came out of this test, which took a few seconds: (It finds each successive maximum number of solutions, if you don't feel like untangling the functional elements)

>>> from functools import reduce
>>> print('\n'.join(
      map(lambda l:' '.join(map(lambda ab:"%dx%d"%ab, l)),
          reduce(lambda a,b: a if len(b) <= len(a[-1]) else a + [b],
                 (finds(n) for n in range(2,1000001)),[[(1,1)]])))) 
1x1
1x2 2x1
1x5 2x2 5x1
1x8 2x3 3x2 8x1
1x14 2x5 3x3 5x2 14x1
1x20 2x7 3x4 4x3 7x2 20x1
1x50 2x17 3x9 4x6 6x4 9x3 17x2 50x1
1x140 2x47 3x24 4x15 7x7 15x4 24x3 47x2 140x1
1x280 4x29 5x20 6x15 7x12 12x7 15x6 20x5 29x4 280x1
1x770 2x257 3x129 4x78 10x17 11x15 15x11 17x10 78x4 129x3 257x2 770x1
1x1430 2x477 3x239 4x144 10x29 11x25 12x22 22x12 25x11 29x10 144x4 239x3 477x2 1430x1
1x3080 2x1027 3x514 4x309 7x112 10x59 11x50 20x21 21x20 50x11 59x10 112x7 309x4 514x3 1027x2 3080x1
1x7700 2x2567 3x1284 4x771 7x277 10x143 11x120 20x43 21x40 40x21 43x20 120x11 143x10 277x7 771x4 1284x3 2567x2 7700x1
1x10010 2x3337 3x1669 4x1002 10x185 11x155 12x132 13x114 20x54 21x50 50x21 54x20 114x13 132x12 155x11 185x10 1002x4 1669x3 3337x2 10010x1
1x34580 2x11527 3x5764 4x3459 7x1237 12x447 13x384 19x188 20x171 38x59 39x57 57x39 59x38 171x20 188x19 384x13 447x12 1237x7 3459x4 5764x3 11527x2 34580x1
1x40040 2x13347 3x6674 4x4005 7x1432 10x731 11x610 12x517 13x444 20x197 21x180 39x64 64x39 180x21 197x20 444x13 517x12 610x11 731x10 1432x7 4005x4 6674x3 13347x2 40040x1
1x100100 2x33367 3x16684 4x10011 7x3577 10x1823 11x1520 12x1287 13x1104 20x483 21x440 25x316 39x141 55x83 65x68 68x65 83x55 141x39 316x25 440x21 483x20 1104x13 1287x12 1520x11 1823x10 3577x7 10011x4 16684x3 33367x2 100100x1
1x340340 2x113447 3x56724 4x34035 7x12157 10x6191 11x5160 12x4367 13x3744 20x1627 21x1480 34x583 39x449 55x239 65x180 84x123 123x84 180x65 239x55 449x39 583x34 1480x21 1627x20 3744x13 4367x12 5160x11 6191x10 12157x7 34035x4 56724x3 113447x2 340340x1
1x760760 2x253587 3x126794 4x76077 7x27172 10x13835 11x11530 12x9757 13x8364 19x4010 20x3629 21x3300 38x1039 39x988 55x512 56x495 65x376 76x285 285x76 376x65 495x56 512x55 988x39 1039x38 3300x21 3629x20 4010x19 8364x13 9757x12 11530x11 13835x10 27172x7 76077x4 126794x3 253587x2 760760x1

【讨论】:

    【解决方案2】:

    我认为关键的细节是@Qudus 正在寻找有 N 个任意大小的正方形的板。

    一个简单的优化是在res &gt; n 时中断。另一个使它快两倍的优化是只在长度 >= 宽度的板上运行它。

    def dims(num_sqrs):
        dim_list=[]
        for i in range(1, num_sqrs + 1):
            temp = []
            for x in range(1, i + 1):
                res = 0
                a = i
                b = x
                while (a != 0) and (b != 0):
                    res = res + (a * b)
                    a = a - 1
                    b = b - 1
                    if res > num_sqrs:
                        break
                if res == num_sqrs:
                    dim_list.append((i, x))
                    if i != x:
                        dim_list.append((x, i))
        print(dim_list)
    

    这里有一个更快的解决方案,它采用不同的方法:

    def dims(num_sqrs):
        dim_list = []
        sum_squares = [0]
        sums = [0] 
        for i in range(1, num_sqrs + 1):
            sums.append(sums[-1] + i)
            sum_squares.append(sum_squares[-1] + i * i)
    
        for i in range(1, num_sqrs + 1):
            if sum_squares[i] > num_sqrs:
                break
            if sum_squares[i] == num_sqrs:
                dim_list.append((i, i))
                break
            for x in range(i + 1, num_sqrs + 1):
                total_squares = sum_squares[i] + sums[i] * (x - i)
                if total_squares == num_sqrs:
                    dim_list.append((x, i))
                    dim_list.append((i, x))
                    break
                if total_squares > num_sqrs:
                    break
        return dim_list
    

    【讨论】:

    • 感谢您理解问题。现在休息快了很多。
    【解决方案3】:

    从基本的代数分析开始。我得出了我自己的公式来计算各种大小的总和。从最初的分析中,我们得到对于大小为 n x m 的板,有(n-k)*(m-k) 个大小为 k 的正方形。对 [0, min(m, n)] 中的 k 求和,我们有一个简单的计算公式:

    sum(((n-k) * (m-k) for k in range(0, min(n, m))))
    

    我将乘积扩展为nm - k(n+m) + k^2,重新推导了各个序列和,并做了一个非迭代公式,假设n

    n * n * m 
    - n * (n - 1) / 2 * (n + m)
    + ((n - 1) * n * (2 * n - 1))/6
    

    第一个链接用更短的公式破坏了我的乐趣:

    t = m - n
    n * (n + 1) / 6 * (2 * n + 3 * t + 1)
    

    从我的继承而来,对术语进行了一些漂亮的重新排列。


    现在进入这个练习的重点:给定所需的正方形数量Q,找出所有具有恰好那么多正方形的矩形尺寸 (n, m)。从上面的公式开始:

    q = n * (n + 1) / 6 * (2 * n + 3 * t + 1)
    

    由于我们得到了Q,即q 的期望值,我们可以遍历n 的所有值,找出是否存在满足公式的t 的正整数值。首先为t解决这个问题:

    t = (6/(n*(n+1)) * q - 2*n - 1) / 3
    

    组合分母:

    t = (6*q) / (3*n*(n+1)) - (2*n + 1)/3
    

    我将使用第一个版本。由于n x m 的解决方案意味着m x n 的解决方案,我们可以将我们的搜索限制为仅n &lt;= m 的那些情况。此外,由于分子缩小(负 n^3 项),我们可以限制对允许 t &gt;= 1n 值的搜索——换句话说,组合分子至少与分母一样大:

    numer = 6 * num_sqrs - n * (n+1) * (2*n+1)
    denom = 3 * n * (n+1)
    

    解决这个问题:

    num_sqrs > (n * (n+1) * (n+2)) / 3
    

    因此,(n 的立方根) / 3 是我们循环限制的方便上限。

    这在代码中为我们提供了一个简单的迭代循环:

    def dims(num_sqrs):
        dim = [(1, num_sqrs)]
        limit = ceil((3*num_sqrs)**(1.0/3.0))
        for n in range(2, limit):
            numer = 6 * num_sqrs - n * (n+1) * (2*n+1)
            denom = 3 * n * (n+1)
            if numer % denom == 0:
                t = numer // denom
                if t >= 0:
                    dim.append((n, n+t))
    
        return dim
    

    几个测试用例的输出:

    >>> print(dims(8))
    [(1, 8), (2, 3)]
    
    >>> print(dims(2000))
    [(1, 2000), (2, 667), (3, 334), (4, 201)]
    
    >>> print(dims(1000000))
    [(1, 1000000), (4, 100001), (5, 66668), (15, 8338), (24, 3341)]
    
    >>> print(dims(21493600))
    [(1, 21493600), (4, 2149361), (5, 1432908), (15, 179118), (24, 71653), (400, 401)]
    

    这些会立即返回,所以我希望这个解决方案对于 OP 的目的来说足够快。

    参数化方程很可能会给我们直接的解决方案,而不是遍历可能性。我会把它留给 Project Euler 的人。 :-)

    【讨论】:

    • 对于1000000,我还得到 (15, 8338) 和 (24, 3341)。
    • 已修复;谢谢。低整数因子让我震惊了一会儿。有一个综述和两个“+1”字词。
    【解决方案4】:

    这使用了 OP 提供的链接中派生的公式。唯一真正的优化是尽量不要查看无法产生结果的维度。 预加载两个结束案例 (figures = [(1,n_squares),(n_squares,1)]) 的结果保存了大量的大量数据。我认为还有其他可以丢弃的块,但我还没有弄清楚。

    def h(n_squares):
        # easiest case for a n x m figure:
        # n = 1 and m = n_squares
        figures = [(1,n_squares),(n_squares,1)]
        for n in range(2, n_squares+1):
            for m in range(n, n_squares+1):
                t = m - n
                x = int((n * (n + 1) / 6) * ((2 * n) + (3 * t) + 1))
                if x > n_squares:
                    break
                if x == n_squares:
                    figures.extend([(n,m),(m,n)])
                    #print(f'{n:>6} x {m:<6} has {n_squares} squares')
            if x > n_squares and n == m:
                break
        return figures    
    

    它也不会列出很多列表,这些列表可能会用 21493600 (400x401) 之类的非常大的数字来炸毁您的计算机。


    从 OP 评论中的链接推导公式(以防资源消失):
    来自http://mathforum.org/library/drmath/view/63172.html的短信

    礼貌:
    - 安东尼博士,数学论坛
    http://mathforum.org/dr.math/

    如果我们有一个 8 x 9 的棋盘,那么正方形的数量如下:

    Size of Square        Number of Squares  
    --------------        -----------------
        1 x 1                8 x 9  = 72
        2 x 2                7 x 8  = 56
        3 x 3                6 x 7  = 42
        4 x 4                5 x 6  = 30
        5 x 5                4 x 5  = 20
        6 x 6                3 x 4  = 12
        7 x 7                2 x 3  =  6
        8 x 8                1 x 2  =  2
      ----------------------------------------
                             Total  = 240
    

    对于 n x m 板的一般情况,其中 m = n + t 我们需要

             n                n
            SUM[r(r + t)]  = SUM[r^2 + rt} 
            r=1              r=1
    
           = n(n + 1)(2n + 1)/6 + tn(n + 1)/2
    
           = [n(n + 1)/6]*[2n + 1 + 3t]
    

    没有。正方形 =

                 [n(n + 1)/6]*[2n + 3t + 1]  .......(1)
    

    在上面的例子中 t = 1 等等

     No. of squares  = 8 x 9/6[16 + 3 + 1]
    
                     = (72/6)[20]
    
                     = 240    (as required)
    

    (n x n+t) 板的一般公式是 (1) 中给出的 以上。

    No. of squares = [n(n + 1)/6]*[2n + 3t + 1]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多