一步的最小步骤

时间:2013-12-12 15:22:31

标签: java algorithm math dynamic-programming

问题陈述:

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

  1. 从中减去1。 (n = n - 1)
  2. 如果它可被2整除,则除以2.(如果n%2 == 0,则n = n / 2)
  3. 如果它可被3整除,则除以3.(如果n%3 == 0,则n = n / 3)。
  4. 现在问题是,给定正整数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)
    4. 我知道使用动态编程并具有整数数组的解决方案。这是代码。

          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,这个解决方案会抛出OutofMemoryError。我不想增加我的堆空间。是否有人使用更少的空间解决方案?

      请注意,使用贪婪的algorthm无法解决这个问题。使用一个while循环并检查可被3整除并可被2整除。您必须使用动态编程。请建议是否有任何解决方案使用更少的空间。< / p>

      例如:

      对于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时失败。

5 个答案:

答案 0 :(得分:5)

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

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

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

deque容器支持随机访问[ ]

编辑:同样如评论中所建议的那样,您应该将数据类型更改为较小的数据类型,因为最大步数应为( (log N) to base 2)

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

答案 1 :(得分:1)

更新:这是更新的代码,我实际测试了一些,我相信从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;
}

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

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;
}

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

答案 2 :(得分:1)

递归解决方案


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];

    } 

答案 3 :(得分:0)

这有效:)

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);    
 }
}

答案 4 :(得分:0)

这是上述问题的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];
    }

// 现在终于有了动态编程方法 最好是这 2 个代码中的并且易于理解 我仍然推荐这个 请先尝试理解递归背后的逻辑,因为 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