【问题标题】:Minimum Steps to One最小步数到一
【发布时间】:2013-12-31 02:11:53
【问题描述】:

问题陈述:

对于一个正整数,您可以执行以下 3 个步骤中的任何一个。

  1. 从中减去 1。 (n = n - 1)
  2. 如果它可以被 2 整除,则除以 2。(如果 n % 2 == 0 ,则 n = n / 2)
  3. 如果它可以被 3 整除,则除以 3。(如果 n % 3 == 0 ,则 n = n / 3)。

现在的问题是,给定一个正整数 n,求从 n 到 1 的最小步数

例如:

  1. 对于 n = 1 ,输出:0
  2. 对于 n = 4 ,输出:2 ( 4 /2 = 2 /2 = 1 )
  3. 对于 n = 7 ,输出:3 ( 7 -1 = 6 /3 = 2 /2 = 1 )

我知道使用动态编程和整数数组的解决方案。这是代码。

    public int bottomup(int n) {
            //here i am defining an integer array
            //Exception is thrown here, if the n values is high.
            public int[] bu = new int[n+1];
            bu[0] = 0;
            bu[1] = 0;
            for(int i=2;i<=n;i++) {
                    int r = 1+bu[i-1];
                    if(i%2 == 0) r = Math.min(r,1+bu[i/2]);
                    if(i%3 == 0) r = Math.min(r,1+bu[i/3]);
                    bu[i] = r;
            }
            return bu[n];
    }

但是我想用更少的空间来解决这个问题。如果 n=100000000,这个解决方案会在 java 中抛出 OutofMemoryError。我不想增加我的堆空间。有人有使用更少空间的解决方案吗?

请注意,使用贪心算法无法解决此问题。使用一个 while 循环并检查可被 3 整除和可被 2 整除将不起作用。您必须使用动态编程。请建议是否有使用更少空间的解决方案。

例如:

对于 n = 10 贪心算法是 10 /2 = 5 -1 = 4 /2 = 2 /2 = 1 这需要 4 个步骤。其中解决方案应该是 10-1 = 9 / 3 = 3 / 3 = 1、3 步。

我什至尝试过自上而下的解决方案。

    public int[] td = null;
    public int topdown(int n) {
            if(n <= 1) return 0;
            int r = 1+topdown(n-1);
            if(td[n] == 0) {
                    if(n%2 == 0) r = Math.min(r,1+topdown(n/2));
                    if(n%3 == 0) r = Math.min(r,1+topdown(n/3));
                    td[n] = r;
            }
            return td[n];
    }

在 n=10000 时失败。

【问题讨论】:

  • 这样做的一种方法,因为步数是 O(log(n)),你可以使用 short 而不是 int 作为 int 输入。
  • 和@elyashiv 一样,byte 也够用了。
  • 您希望能够处理多高的n?空间方面的考虑可能为什么像SPOJ 这样的练习将其限制为(0 &lt; n ≤ 2*10^7 )
  • char 优于 short,因为答案永远不会是否定的。 chars 未签名,shorts 已签名。
  • 我认为short可以签名。

标签: java algorithm math dynamic-programming


【解决方案1】:

一个想法是,在任何迭代中,您只需要 r/3r 的值。所以你可以继续丢弃数组的1/3rd

我不熟悉Java,但是对于C++,您可以使用double ended queue (deque)

你不断从后面添加到双端队列。
i = 6 时,您不需要bu[0]bu[1]。所以你从队列的前面弹出两个元素。

双端队列容器支持随机访问[ ]

编辑:同样按照 cmets 的建议,您应该将数据类型更改为更小的数据类型,因为最大步数应为 ( (log N) to base 2)

EDIT2:正如 Dukeling 所指出的,在 Java 中似乎没有现成的适合双端队列的实现,不会影响时间复杂度。您可以像 C++ 一样考虑以自己的方式实现它(我听说它是​​作为向量的向量实现的,与元素总数相比,内部向量的大小很小)。

【讨论】:

  • 有趣的想法。实现它的实际方法是什么?某种队列?
  • ArrayDeque 可能是您正在寻找的。 deque ADT 本身不支持恒定时间随机访问,因为它也可以实现为链表。
  • 应该注意,在渐近情况下,这种方法的内存使用量仍然是 n/3 -> O(n)
  • 实际上我收回我之前的评论 - ArrayDeque 使用模板,上次我检查过的模板,与 C++ 不同,不能使用原语,所以它将使用原语包装类,它使用了相当一点记忆。您可能需要自定义实现。
【解决方案2】:

更新:这是我实际测试过的更新代码,我相信对于从 1 到 100000 的 n 得出相同的答案。我将原始答案留在下面以供参考。缺陷是对 MAX_INT 的“巧妙”使用。我忘记了在某些情况下我会跳过 -1 的可能性,但该数字也不能被 2 或 3 整除。这通过返回 null 表示“这条路径对进一步探索毫无意义”来解决这个问题。

public static int steps(int n) {
    return steps(n, 0);
}
private static Integer steps(int n, int consecutiveMinusOnes) {
    if (n <= 1) {
        return 0;
    }
    Integer minSteps = null;
    if (consecutiveMinusOnes < 2) {
        Integer subOne = steps(n - 1, consecutiveMinusOnes + 1);
        if (subOne != null) {
            minSteps = 1 + subOne;
        }
    }
    if (n % 2 == 0) {
        Integer div2 = steps(n / 2, 0);
        if (div2 != null) {
            if (minSteps == null) {
                minSteps = div2 + 1;
            } else {
                minSteps = Math.min(minSteps, 1 + div2);
            }
        }
    }
    if (n % 3 == 0) {
        Integer div3 = steps(n / 3, 0);
        if (div3 != null) {
            if (minSteps == null) {
                minSteps = div3 + 1;
            } else {
                minSteps = Math.min(minSteps, 1 + div3);
            }
        }
    }
    return minSteps;
}

我相信这可能有效,但我还没有证明这一点。该算法基于这样的想法,即减一的唯一原因是让您更接近可被 2 或 3 整除的数字。因此,您实际上不需要两次以上的减一步骤连续,因为如果 k % 3 == 2,那么 k - 2 % 3 == 0 并且你可以除以三。再减一次将是浪费精力(您还将通过至少一个偶数,因此最好的两步除法机会将出现)。这意味着一种自上而下的递归方法,如果您愿意,可以混合一些记忆:

public static int steps(n) {
    return steps(n, 0);
}
private static int steps(int n, int consecutiveMinusOnes) {
    if (n <= 1) {
        return 0;
    }
    int minSteps = Integer.MAX_VALUE;
    if (consecutiveMinusOnes < 2) {
        minSteps = 1 + steps(n - 1, consecutiveMinusOnes + 1);
    }
    if (n % 2 == 0) {
        minSteps = Math.min(minSteps, 1 + steps(n / 2, 0));
    }
    if (n % 3 == 0) {
        minSteps = Math.min(minSteps, 1 + steps(n / 3, 0));
    }
    return minSteps;
}

免责声明:正如我上面所说,我还没有证明这种方法有效。我也没有测试过这个特定的实现。我也没有做记忆的东西,只是因为我很懒。无论如何,我希望即使这不太奏效,它也能给你一些关于如何修改方法的想法。

【讨论】:

  • @RadhaKrishna,你的意思是它给出了错误的答案吗?还是抛出异常?还是只是挂起?我测试了一个高达 n=10,000 的 Python 端口,它似乎对我有用。
  • Sorry n > 10000.我在java中试过。它在线程“main”java.lang.StackOverflowError中给出了以下错误异常
  • 我意识到这个特定的实现存在缺陷。将很快发布带有修复的编辑。
  • 我已经根据原始问题中发布的版本测试了我的更新版本,发现它似乎适用于 n
  • n 是 1000 倍时它会失败吗? OP 有一个高达 100M 的工作解决方案。
【解决方案3】:

这行得通:)

import java.util.Scanner;
public class MinimumStepToOne {

 public static void main(String[] args){
    Scanner sscan = new Scanner(System.in);
    System.out.print("Give a no:" + " ");

    int n = sscan.nextInt();
    int count = 0;
    for(int i = 0; n > 1; i++){

        if(n%2 == 0){n /= 2; count++;}
        else if(n%3 == 0){ n /= 3; count++;}
        else { n -= 1; count++;}

    }
    System.out.println("your no is minimized to: " + n);
    System.out.println("The min no of steps: " + count);    
 }
}

【讨论】:

  • 值为10时的结果
【解决方案4】:

这个问题的递归解决方案


public static int countMinStepsTo1(int n){

      if(n==1)
      {
          return 0;
      } 
      int count1,count2=Integer.MAX_VALUE,count3=Integer.MAX_VALUE;

      count1 = countMinStepsTo1(n-1);

       if((n%2)==0)
       {
           count2=countMinStepsTo1(n/2);
       }
        if((n%3)==0)
        {
            count3=countMinStepsTo1(n/3);
        }

        return 1+Math.min(count1,Math.min(count2,count3));
    }

DP 方法解决这个问题

public static int countstepsDP(int n)
    {
        int storage[] = new int[n+1];
        storage[1]=0;

        for(int i=2; i<=n;i++)
        {
            int min=storage[i-1];
            if(i%3==0)
            {
                if(min>storage[i/3])
                {
                    min=storage[i/3];
                }
            }
            if(i%2==0)
            {
                if(min>storage[i/2])
                {
                    min=storage[i/2];
                }
            }
            storage[i]=1+min;
        }

        return storage[n];

    } 

【讨论】:

  • 您测试过这个解决方案吗?小n 似乎没问题,但即使n = 1000 也不会产生结果,并且对于由 OP 指定的 n = 100_000_000 的输入,它会立即导致 StackOveflowError,因为它尝试使用 (n-1) 进行 n 次调用.
  • 它会给你结果,但由于这个程序的复杂性,它需要时间来完成,因为它是 n 次方的 2 次方。您可以尝试 DP 方法来解决这个问题,它适用于 n=100000000
【解决方案5】:

这是上述问题的 c++ 递归解决方案

// 递归方法


    int minSteps(int n)
    {
        if (n == 1)
        {
            return 0;
        }
        int x, y = INT_MAX, z = INT_MAX;
        // Brute Force Approach
        x = minSteps(n - 1);
        if (n % 2 == 0)
        {
            y = minSteps(n / 2);
        }
        if (n % 3 == 0)
        {
            z = minSteps(n / 3);
        }
        return 1 + min(x, min(y, z));
    }

这是记忆技术:

// 记忆 这可能看起来很困难,但如果你知道递归是如何工作的,请相信我,记忆是大幅提高复杂性的最佳方法


    int memoMiniSteps(int n)
    {   
        int * arr = new int[n];
        for(int i = 0 ;i<n ;i++){
            arr[i]=-1;
        }
        if (n == 1)
        {
            return 0;
        }
        int x, y = INT_MAX, z = INT_MAX;
        // Memoization Approach
        if(arr[n-1]!=-1){
            x = arr[n-1];
        }else{
            x = minSteps(n - 1);
            arr[n-1]=x;
        }
    
        if (n % 2 == 0)
        {   
            if(arr[n/2]!=-1){
                y=arr[n/2];
            }else{
                y = minSteps(n / 2);
                arr[n/2]=y;
            }
        }
        if (n % 3 == 0)
        {
            if(arr[n/3]!=-1){
                y=arr[n/3];
            }else{
                y = minSteps(n / 3);
                arr[n/3]=y;
            }
        }
        arr[n]= 1 + min(x, min(y, z));
        return arr[n];
    }

// 现在终于动态编程方法这两个代码中最好的并且易于理解我仍然推荐这个请尝试首先了解递归背后的逻辑,因为 Dp 完全是为了优化问题而没有别的

DP 方法


    int DpMinCount(int n){
        int *a = new int[n+1];
        a[0]=0;
        a[1]=0;
        for(int i =2 ; i<n+1 ;i++){
            int ans = a[i-1]+1;
            if(i%2==0){
                ans = min(ans,a[i/2]+1);
            }
            if(i%3==0){
                ans = min(ans,a[i/3]+1);
            }
        a[i]=ans;
        }
        return a[n];

}

图示:

enter image description here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 2019-09-09
    • 2015-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-28
    相关资源
    最近更新 更多