【问题标题】:Given an integer N and a set of operations, reduce N to 1 in the least amount of steps给定一个整数 N 和一组操作,以最少的步骤将 N 减少到 1
【发布时间】:2014-02-01 13:12:53
【问题描述】:

在给定的整数 N 上,我们可以使用以下操作:

  1. 如果 N 可以除以 3:除以 3。
  2. 如果 N 可以被 2 整除:除以 2。
  3. 减去 1。

如何找到以最少步数达到 1 的策略?

【问题讨论】:

  • 你是说正确的解是 12,因为 ((12 / 3) / 2) - 1 = 1?
  • 是的。在 10 中,((10-1)/3)3) 是正确的解决方案,而不是 (((10/2)-1)/2))/2。
  • @Nabla 一个就够了。在 10 中,答案是 3。
  • 这让我想起了Collatz conjecture,3n+1 是此处两个步骤的倒数,/2 涵盖了第三个步骤。我想知道这是否表明有比蛮力更好的解决方案。也许值得在数学 SE 网站中讨论一个主题。
  • @HyeonWooKim 如果 N 不能被 3,2 整除,那该怎么办?

标签: algorithm


【解决方案1】:

正如 mbeckish 所提到的,您可以将其视为 BFS 遍历,与自下而上的 DP 方法相比,它具有明显更好的时间和空间复杂性。您还可以在遍历中应用各种分支定界 (B&B) 启发式算法,以便在我们之前已经看到标记值的节点处修剪树的分支。与实际的 B&B 启发式不同,这不会删除最优解,因为它不涉及对最优解可能在哪里的任何有根据的猜测。我将给出一个直观的示例,并将算法降低到 0 以更好地说明。

这是一个将 10 减少到 0 的完整操作树:

   --------10---------
   5         -----9----
---4---     -3-      ------8------  
2    -3-    1 2    --4--         7        
1    1 2    0 1    2  -3-   -----6------   
0    0 1      0    1  1 2   2   -3-    5   
       0           0  0 1   1   1 2  --4-- 
                        0   0   0 1  2  -3-
                                  0  1  1 2
                                     0  0 1
                                          0 

由于我们正在执行 BFS,我们实际上会像下面这样在第一个零处停止,而不是构建树的更深部分:

   --------10------
   5         -----9--------
---4---     -3-     ------8------  
2    -3-    1 2     4           7        
1    1 2    0 

但是,我们可以通过 B&B 启发式进一步减少分支的数量,使其看起来像这样(这对大量数字产生了巨大的影响):

   --------10------
   5         -----9--------
   4         3            8
   2         1            7
             0 

时间复杂度: O(log n) 空间复杂度: O(log n) (我认为)

以下是输入为 1 googol (10^100) 的 python 3 代码,在我的计算机上运行大约需要 8 秒,内存约为 350 MB。您也可以通过https://repl.it/B3Oq/76在线运行它

from collections import deque

def number_of_steps(i):
    Q = deque()
    seen_before = set()
    steps = 0
    Q.append((i, steps))
    while True:
        j, steps = Q.popleft()
        if j == 1:
            return steps
        if j % 3 == 0:
            branch(Q, seen_before, steps, j // 3)
        if j % 2 == 0:
            branch(Q, seen_before, steps, j // 2)
        branch(Q, seen_before, steps,  j - 1)

def branch(Q, seen_before, steps, k):
    if k not in seen_before:
        seen_before.add(k)
        Q.append((k, steps + 1))

import time
n = 10**100
print('input:', n)
start = time.time()
steps = number_of_steps(n)
end = time.time()
print('runtime (seconds):', end - start)
print('number of steps:', steps)

【讨论】:

    【解决方案2】:

    有快速的动态规划解决方案:-

    minSteps(N) = Minimum(minSteps(N/3),minSteps(N/2),minSteps(N-1)) + 1
    

    注意:如果 N 不能被 3 或 2 整除,则不要将其包含在 DP 方程中。

    时间复杂度: O(N) 空间复杂性: O(N)

    用于 DP 解决方案的 Java 代码:-

    public static int decompose(int n) {
            int steps [] = new int[n+1];
            steps[1] = 0;
            for(int i=2;i<=n;i++) {
                int min = n;
                if(i%2==0) {
                    min = Math.min(min,steps[i/2]);
                }
                if(i%3==0) {
                    min = Math.min(min,steps[i/3]);
                }
                min = Math.min(min,steps[i-1]);
                steps[i] = min + 1;
            }
            int k =n;
            System.out.println("Steps:");
            while(k>1) {
                if(k%3==0&&steps[k/3]+1==steps[k]) {
                    System.out.println("div 3");
                    k=k/3;
                }
                else if(n%2==0&&steps[k/2]+1==steps[k]) {
                    System.out.println("div 2");
                    k=k/2;
                }
                else {
                    System.out.println("minus 1");
                    k=k-1;
                }
            }
    
            return(steps[n]);
    
        }
    

    【讨论】:

    • 我不知道更好的方法,但我不会说这是“快”。 O(N) 是输入大小的指数(因为N 可以用log N 位编码)。解决这个问题的唯一真正快速的算法必须是某种贪婪的方法。请注意,如果我们省略第一条规则,则有一个简单的解决方案可以在O(log N) 步骤中达到 1:偶数时除以 2,奇数时减 1。所以这是整个问题的明显上限。
    • @Heuster 我的意思是它比 N 中的指数更快,如果你用蛮力的话。是的,以下解决方案是像背包一样的伪多项式,但像背包一样,这是一个难题。而且你提到的 O(logN) 是下限(你写了上限),但不知道有什么算法可以实现它。
    • 我的意思是步数的上限 :-) 仅使用规则 2 和 3,最佳步数正好是(查看数字的二进制表示)number of 1 bits - 1 + position of highest 1 bit,因为最高有效位必须通过除以 2 (position of highest 1 bit 次)右移,并且必须通过在适当的步骤中减去 1 来消除所有其他位。添加规则一可能会减少消除步骤,因此您可以限制搜索空间。
    • @Heuster 是的,它是没有步数的上限,但是要使用分支和界限,您可能需要一个下限,因为我们正在寻找最小步数。 log3(N) 是一个下限,但不是我假设的那么紧。
    【解决方案3】:

    将此视为广度优先树遍历。

    根节点是 N。操作是通向其子节点的边,

    到达 1 时停止。

    从根到1的路径就是解。

    【讨论】:

    • 但是通过三叉树的广度优先搜索不会变得非常慢非常快吗?
    • @mbeckish 也许你更喜欢 dijkstra 的最短路径而不是 BFS 来获得最短路径,这需要 O(NlogN) 才能获得从根到一个的最短路径,但仍然比 DP 解决方案慢。
    • @Teepeemm - 可能,但可能会有很多短路,因为大多数数字都不能被 2 和 3 整除。此外,由于进行了很多除法,我想象一下深度将在 log N 左右(其中 N 是给定的整数)。
    猜你喜欢
    • 2017-01-28
    • 1970-01-01
    • 2022-06-16
    • 1970-01-01
    • 1970-01-01
    • 2016-02-02
    • 2017-09-27
    • 1970-01-01
    • 2020-03-26
    相关资源
    最近更新 更多