问题陈述:
在正整数上,您可以执行以下3个步骤中的任何一个。
现在问题是,给定正整数n,找到n到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,这个解决方案会抛出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时失败。
答案 0 :(得分:5)
一个想法是,在任何迭代中,您只需要r/3
到r
的值。所以你可以继续丢弃数组的1/3rd
。
我不熟悉Java
,但使用C++
您可以使用double ended queue (deque)
:
你继续从后面添加双端队列
在i = 6
时,您不需要bu[0]
和bu[1]
。所以你从队列的前面弹出两个元素。
deque容器支持随机访问[ ]
。
编辑:同样如评论中所建议的那样,您应该将数据类型更改为较小的数据类型,因为最大步数应为( (log N) to base 2)
答案 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];
}
图示: