找到最少的移动次数

时间:2014-03-15 17:29:00

标签: java algorithm math dynamic-programming

我有以下问题陈述:

  

给定数n(1

到目前为止,我已经编写了以下代码以尝试解决问题:

while(n!=1){

    if(n%3==0 || n%2==0){
        if(n%3==0){
            n=n/3;
            c=c+1;
        }
        if(n%2==0){
        n=n/2;
        c=c+1;
        }
    }
    else{
        n=n-1;
        c=c+1;
    }
}
System.out.println(c);

但我没有得到所需的输出。有人可以帮助我。

8 个答案:

答案 0 :(得分:7)

我认为特里斯坦是对的 - 你无法知道哪个操作最终会产生最短的路径,所以你必须尝试所有这些才能得到正确的答案。

通常,像这样的强力解决方案意味着计算将采用指数时间。您从 n 开始,然后使用3个操作计算最多3个新数字。然后对于这3个数字中的每一个,你得到另外3个,共9个。然后对于这9个中的每一个,你得到另外3个,总共27个;等等。你可以看到如何快速结束一系列荒谬的可能性。

但是,此处的搜索空间有限。由于您的所有操作都会导致值减少,因此您只会遇到从1到 n 的数字,包括1和 n 。这意味着它最多需要 n 操作才能达到你的目标1.每个数字只有一条最短路径,所以一旦你找到了这条路,你就不需要考虑任何您找到的其他路径会导致相同的数字。如果你保留一组以前看过的数字,你应该能够消除大部分的搜索空间,因为你可以抛弃重复的结果。 (这是Memoization的一种形式。)

以下是我将如何解决这个问题:

  1. 创建Set<Integer>以包含以前看到的值。
  2. 创建Map<Integer, Integer>以保留有效值。每个键→值条目的键将是从 n 1的路径中的数字,该值将是达到该值时所需的操作数号。
  3. 将初始条目 n 0放入有效地图。
  4. 当您的有效地图不包含值为1的键(您的目标)时:
    1. 创建一个空地图以保存新的有效值。
    2. 对于有效 x i 中的每个条目:
      1. 如果 x 可被3整除且 x / 3不在看见集中,则添加 x / 3到看到并将 x / 3→ i +1放入新的有效地图。
      2. x / 2和 x -1做类似的事情。
    3. 将当前有效地图替换为新的有效地图。
  5. 有效地图中为条目1 i 返回值 i
  6. 您可以采取一些措施来加快速度(例如,当您找到1时立即突破循环),或者减少内存占用(例如,您从所看到的集合中丢弃哨兵)如果它们比你的任何活动条目都大,或者使用列表而不是地图,因为迭代的所有 i 值都是相同的),但这应该足够有效地做你做的事情需要。


    我已将我的解决方案移植到Java并在此处发布:

    http://ideone.com/qWt0LE

    输出包含一些时间。请注意,此处链接的解决方案使用看到的地图和活动的列表。我将链中的前一个数字存储为中看到的每个地图条目的值,这样我就可以在最后重建链。在输出中,3表示除以3 ,2表示除以2 ,1表示减去1

答案 1 :(得分:2)

这是一个值得思考的有趣案例。 从10开始,你有几个选择:

10 / 2  = 5     1 move
5 - 1   = 4     2 moves
4 / 2   = 2     3 moves
2 - 1   = 1     4 moves


10 - 1  = 9     1 move
9 / 3   = 3     2 moves
3 / 3   = 1     3 moves

距离被2整除的数字怎么样? 从11开始,我们有以下选择:

11 - 1  = 10    1 move
10 / 2  = 5     2 moves
5 - 1   = 4     3 moves
4 / 2   = 2     4 moves
2 / 2   = 1     5 moves


11 - 1  = 10    1 move
10 - 1  = 9     2 moves
9 / 3   = 3     3 moves
3 / 3   = 1     4 moves

也许这只有在你减去的数字是否可以被3整除时才有效?谁知道,好运OP。

答案 2 :(得分:2)

最简单的解决方案可能是探索所有可能性。

public static ArrayList<Integer> solve(int n, 
  ArrayList<Integer> moves, int bestMove,HashMap<Integer,Integer> memory) {

        if (moves.size() >= bestMove) return null;
        if (n == 1) return moves;
        Integer sizeOfPathN= memory.get(n);

        if (sizeOfPathN!=null && sizeOfPathN<=moves.size())return null;
        memory.put(n,moves.size());

        int size_1=Integer.MAX_VALUE, size_2 = Integer.MAX_VALUE, size_3 = Integer.MAX_VALUE;
        ArrayList<Integer> moves3 = null, moves2 = null, moves1;

        if (n % 3 == 0) {
            ArrayList<Integer> c = new ArrayList<Integer>(moves);
            c.add(3);
            moves3 = solve(n / 3, c,bestMove,memory);
            if (moves3!=null)
            size_3 = moves3.size();
        }

        bestMove = Math.min(bestMove, size_3);

        if (n % 2 == 0) {
            ArrayList<Integer> c = new ArrayList<Integer>(moves);
            c.add(2);
            moves2 = solve(n / 2, c,bestMove,memory);
            if (moves2!=null)
            size_2 = moves2.size();
        }

        bestMove = Math.min(bestMove, size_2);


        ArrayList<Integer> c = new ArrayList<Integer>(moves);
        c.add(1);
        moves1 = solve(n - 1, c,bestMove,memory);
        if (moves1!=null)
        size_1 = moves1.size();

        int r = Math.min(Math.min(size_1, size_2),size_3);
        if (r==size_1) return moves1;
        if (r==size_2) return moves2;

        return moves3;

    }

<强>解释

n:n

moves:包含运动的ArrayList。 (用于打印pourposes)

bestMove:包含找到的最小解决方案大小的值。

memory:包含&#34;状态&#34;的HashMap先前探讨了路径的长度。

如果我们打电话 public static void main(String [] args){

    long a = System.currentTimeMillis();
    Object[] sol=solve(10, new ArrayList<Integer>(),Integer.MAX_VALUE,new HashMap<Integer,Integer>()).toArray();
    System.out.println(sol.length);
    System.out.println(Arrays.toString(sol));
    System.out.println((System.currentTimeMillis()-a));
}

输出结果为:

3
[1, 3, 3]
1

相当于n-1, n/3,n/3(@Tristan的最佳解决方案)

如果我们将1000 000 000称为n:

30
[1, 3, 3, 3, 3, 1, 3, 3, 1, 3, 1, 1, 3, 3, 3, 3, 1, 2, 2, 1, 3, 2, 1, 3, 3, 2, 1, 3, 2, 2]
55

如果我们使用11调用它:

4
[1, 1, 3, 3]
1

修改 如果只需要移动次数:

public static int solve(int n,int moves,int bestMove,HashMap<Integer,Integer> memory) {

        if (moves >= bestMove) return Integer.MAX_VALUE;
        if (n == 1) return moves;
        Integer sizeOfPathN= memory.get(n);

        if (sizeOfPathN!=null && sizeOfPathN<=moves)return Integer.MAX_VALUE;
        memory.put(n,moves);

        int size_1=Integer.MAX_VALUE;
        int size_2 = Integer.MAX_VALUE;
        int size_3 = Integer.MAX_VALUE;

        moves=moves+1;
        if (n % 3 == 0) size_3 = solve(n / 3, moves,bestMove,memory);
        bestMove = Math.min(bestMove, size_3);      
        if (n % 2 == 0) size_2=solve(n >> 1, moves,bestMove,memory);

        bestMove = Math.min(bestMove, size_2);

        size_1 = solve(n - 1, moves,bestMove,memory);


        return  Math.min(Math.min(size_1, size_2),size_3);


    }

使用

调用此方法
long a = System.currentTimeMillis();
System.out.println(
     solve(1000 *1000*1000, 0,Integer.MAX_VALUE,new HashMap<Integer,Integer>()));

    System.out.println((System.currentTimeMillis()-a));

输出:

30
24

足够快

答案 3 :(得分:2)

我认为这里的问题是计算最小步数,而不是关于步骤的详细信息,如何处理它等等。因此,在这种情况下,解决方案应该是高效且最简单的。动态编程中的自下而上的方法。

  int getMinOperations(int n) {
    // Use this array to store the previous solved result.
    int dp[] = new int[n + 1];
    // base case, if it is 1, we do not need to do any processing
    dp[1] = 0;

    for (int i = 2; i <= n; i++) {
        // for the time being, let's assume we are getting minimum number of step by subtracting 1
        dp[i] = 1 + dp[i - 1];
        // if number if divisible by 2 let's divide it and compare it with the number of steps 
        // calculated in previous step, choose the minimum one
        if (i % 2 == 0)
            dp[i] = Math.min(dp[i], 1 + dp[i / 2]);
        // if number if divisible by 3 let's divide it and compare it with the number of steps 
        // calculated in previous step, choose the minimum one
        if (i % 3 == 0)
            dp[i] = Math.min(dp[i], 1 + dp[i / 3]);
        // At this step we have stored the minimum step to reduce the i to 1. and we will continue till nth value
    }
       // Returning nth value of array, as value at each index is the minimum number of steps to reduce it to 1.
    return dp[n];
}

答案 4 :(得分:0)

注意:这不是实际代码,你需要考虑极端情况,这是一个伪代码,它给出了关于算法的逻辑

这可以递归完成,但由于n非常高,你需要记住使用额外的空间。

    fun(int n)
   {
        if(n==0) return INFINITY;
        if(n==1) return 0;  

        if(n%3==0)
        a = fun(n/3)

        if(n%2==0)
        b = fun(n/2)

        return min(fun(n-1) , a+1 , b+1)
}

答案 5 :(得分:0)

使用动态编程的两种解决方案的Python实现

1)自上而下

def calculate(mins, n):
    if n<= 0:
        raise ValueError("input is not positive")
    if n == 1:
        return 0
    if (mins[n] != -1):
        return mins[n]

    result = 1 + calculate(mins, n-1)
    if (n % 2 == 0):
        result = min(result, 1 + calculate(mins, n // 2))  
    if (n % 3 == 0):
        result = min(result, 1 + calculate(mins, n // 3))

    mins[n] = result
    return result

def get_min_step_memoization (n):
    mins = [-1] * (n+1)
    return calculate(mins, n)

2)自下而上

def get_min_step_dp(n):
    if n<= 0:
        raise ValueError("input is not positive")
    results = [-1] * (n+1)
    results[1] = 0 

    for idx in range(2, n+1):
        results[idx] = 1 + results[idx-1]
        if (idx % 2 == 0):
            results[idx] = min(results[idx], 1 + results[idx//2])
        if (idx %3 == 0):
            results[idx] = min(results[idx], 1 + results[idx//3])

    return results[n]

答案 6 :(得分:0)

使用记忆速度。

初始化你的地图:

n_to_steps = {1=>0, 2=>1, 3=>1}

现在计算。不要计算从1到n的所有内容......而是:

def f(n)
  if n isn't a key in our map
    n_to_steps[n] = min(1 + n%2 + f(n/2), 1 + n%3 + f(n/3))
  end
  return n_to_steps[n]
end

答案 7 :(得分:-1)

我认为n= 10^9指向一些逻辑,不只是尝试所有可能的情况和所有排列 这是我得到的:

1 -> 0  
2 -> 1  
3 -> 1  
4 -> 2  
5 -> 3  
6 -> 2  
7 -> 3  
8 -> 3  
9 -> 2  
10 ->3  
11 ->4  
12 ->3  
13 ->4  
14 ->4  
15 ->3  
16 ->4  
17 ->5  
18 ->3  
19 ->4  
20 ->4  
21 ->4  

所以我认为fornula是:

    while(n!=1)
    {
     if(n%3==0)
       ans += 1  and  n/=3 
      else if (n%2==0)
       ans += 1  and  n/=2
      else n--  , ans+=1
    }  

在0.06秒内为N = 1000000000提供答案。 http://ideone.com/0pfKoz

// 以前不正确的逻辑(对于@Dukeling指出的n = 30很容易失败)

while(n!=1)
{
 if(n%2==0 && n%3==0)
   ans += p1 + p2   and  n=1  {where n = 2^p1 * 3^p2 }
  else if (n%2!=0 && n%3 !=0)
   n--
  else if (n%2==0 && n%3 !=0)
   ans+=1  and n/=2 
  else if (n%2!=0 && n%3 ==0)
   ans+=1  and n/=3 
}   

请澄清您是否已获得此问题的答案,并尝试查看我的逻辑是否有效。