【问题标题】:Minimum number of operations to get from source to target.从源到目标的最小操作数。
【发布时间】:2018-12-29 20:20:55
【问题描述】:

我在一次采访中遇到了这个问题 - 以最少的操作数将数字源转换为目标。

允许的操作

  1. 乘以 2。
  2. 加1。
  3. 减1。

0

我尝试了天真的递归路线(O(3 ^ n)),即。在每个级别减 1,加 1 并乘以 2,以尝试找到可以扩展到动态编程但由于无限循环而无法扩展的解决方案。

//Naive approach Via Recursion 

int minMoves(int source, int target){

    if(source <1 || source > target){
        return -1;
    }

    int moves =0;
    // Potential infinite loop - consider 3,6-> 2,6- >1,6->(0,6)x (2,6)->1,6->(0,6)x (1,6)->(0,6)x (2,6)->1,6..
    int movesLeft  = minMoves(source -1, target) ==-1? Integer.MAX_VALUE:minMoves(source -1, target);
    int movesRight  = minMoves(source +1, target) ==-1? Integer.MAX_VALUE:minMoves(source +1, target);
    int moves2X  = minMoves(2*source, target) ==-1? Integer.MAX_VALUE:minMoves(2*source, target);

    moves = 1+ Math.min(Math.min(movesRight,movesLeft), moves2X);
    return moves;

}

关于如何调整我的解决方案的任何想法?或者可能有更好的方法来解决它?

【问题讨论】:

  • 递归代码有效吗?
  • 它没有,我在代码中有一个示例,我认为它通过进入无限循环而失败。

标签: algorithm language-agnostic dynamic-programming


【解决方案1】:

如果您将解决方案视为图遍历,其中每个节点都是您可以生成的中间值,那么您的递归解决方案就像深度优先搜索 (DFS)。您必须完全扩展,直到您尝试了搜索空间“分支”中的所有解决方案,然后才能在其他任何地方继续。如果您有一个无限循环,这意味着即使存在较短的路径,它也永远不会终止,即使您没有无限循环,您仍然必须搜索解决方案空间的其余部分以确保其最优。

请考虑一种类似于广度优先搜索 (BFS) 的方法。您均匀地向外扩展,并且永远不会搜索比最优解更长的路径。只需使用 FIFO 队列来安排接下来要访问的节点。这是我在求解器中采用的方法。

from queue import Queue

def solve(source, target):
    queue = Queue()
    path = [source]
    queue.put(path)
    while source != target:
        queue.put(path + [source * 2])
        queue.put(path + [source + 1])
        queue.put(path + [source - 1])

        path = queue.get()
        source = path[-1]
    return path

if __name__ == "__main__":
    print(solve(4,79))

【讨论】:

  • 尽管为了完整起见,您可能需要 path.length/ size (或者在 Python 中调用它)
【解决方案2】:

在保持递归实现的同时加快(并可能修复)此代码的一种方法是使用memoization

这里的问题是您要多次重新计算相同的值。相反,您可以使用 map 来存储您已经计算的结果,并在您再次需要时重复使用它们。

【讨论】:

    【解决方案3】:

    这个问题可以建设性地解决。首先,简单的案例。如果 s=t,则答案为 0。如果 s > t,则答案为 s-t,因为减 1 是唯一降低 s 的操作,而其他两个只能增加所需的减法次数。

    现在让我们假设 s 0,所以加倍始终是最快的增加方式(如果 s 为 1,则与递增相关)。因此,如果挑战是使 s >= t,那么答案将始终是做到这一点所需的倍增次数。此过程可能会超出 t,但大于 t 的第一次加倍和不大于 t 的最后一次加倍必须在 t 的 2 倍范围内。

    让我们看看当我们做加法或减法时的效果。首先,只看加法:

    (((s*2) * 2) * 2) + 1 = 8s + 1
    

    对比:

    ((((s+1)*2) * 2) * 2) = 8s + 8
    

    在 n 个加倍之前加一个会使最终结果变大 2^n。所以考虑如果 s 是 3 并且 t 是 8。最后一个不大于 8 的双精度是 6。这是 2 关闭,所以如果我们在最后一个双精度前加一个 1 双精度,我们得到我们想要的: (3+1) * 2. 或者,我们可以尝试超调到大于 8 的第一个双倍,即 12。这是 4 关,所以我们需要在最后一个之前减两个倍: (3-1)*2*2 = 8

    一般来说,如果我们低于目标 x,如果 x 的二进制表示在第 n 位有 1,我们需要在最后 n 倍之前放置一个+1

    同样,如果我们比目标高 x,我们也会对 -1's 做同样的事情。

    这个过程对于 x 的二进制表示中的1 的位置超过了加倍的数量没有帮助。比如s = 100, t=207,只有1个加倍,但是x是7,也就是111。我们可以先做一个加法,把中间的一个去掉,剩下的一个一个(s+1)*2 + 1 + 1 + 1 + 1 + 1

    这是一个具有调试标志的实现,该标志还可以在定义该标志时输出操作列表。运行时间为 O(log(t)):

    #include <iostream>
    #include <string>
    #include <sstream>
    
    #define DEBUG_INFO
    
    int MinMoves(int s, int t)
    {
        int ans = 0;
        if (t <= s)
        {
            return s - t; //Only subtraction will help
        }
        int firstDoubleGreater = s;
        int lastDoubleNotGreater = s;
        int nDouble = 0;
        while(firstDoubleGreater <= t)
        {
            nDouble++;
            lastDoubleNotGreater = firstDoubleGreater;
            firstDoubleGreater *= 2;
        }
        int d1 = t - lastDoubleNotGreater;
        int d2 = firstDoubleGreater - t;
        if (d1 == 0)
            return nDouble -1;
        int strat1 = nDouble -1;        //Double and increment
        int strat2 = nDouble;           //Double and decrement
    
    
    #ifdef DEBUG_INFO
    std::cout << "nDouble: " << nDouble << "\n";
        std::stringstream s1Ops;
        std::stringstream s2Ops;
        int s1Tmp = s;
        int s2Tmp = s;
    #endif
    
        int mask = 1<<strat1;
        for(int pos = 0; pos < nDouble-1; pos++)
        {
    #ifdef DEBUG_INFO
            if (d1 & mask)
            {
                s1Ops << s1Tmp << "+1=" << s1Tmp+1 << "\n" << s1Tmp+1 << "*2= " << (s1Tmp+1)*2 << "\n";
                s1Tmp = (s1Tmp + 1) * 2;
            }
            else
            {
                s1Ops << s1Tmp << "*2= " << s1Tmp*2 << "\n";
                s1Tmp = s1Tmp*2;
            }
    #endif
            if(d1 & mask)
                strat1++;
            d1 = d1 & ~mask;
            mask = mask >> 1;
        }
        strat1 += d1;
    #ifdef DEBUG_INFO
        if (d1 != 0)
            s1Ops << s1Tmp << " +1 " << d1 << " times = " << s1Tmp + d1 << "\n";
    #endif
    
        mask = 1<<strat2;
        for(int pos = 0; pos < nDouble; pos++)
        {
    #ifdef DEBUG_INFO
            if (d2 & mask)
            {
                s2Ops << s2Tmp << "-1=" << s2Tmp-1 << "\n" << s2Tmp-1 << "*2= " << (s2Tmp-1)*2 << "\n";
                s2Tmp = (s2Tmp-1)*2;
            }
            else
            {
                s2Ops << s2Tmp << "*2= " << s2Tmp*2 << "\n";
                s2Tmp = s2Tmp*2;
            }
    #endif
    
    
            if(d2 & mask)
                strat2++;
            d2 = d2 & ~mask;
            mask = mask >> 1;
        }
        strat2 += d2;
    #ifdef DEBUG_INFO
        if (d2 != 0)
            s2Ops << s2Tmp << " -1 " << d2 << " times = " << s2Tmp - d2 << "\n";
    
    
    
        std::cout << "Strat1:   "  << strat1 << "\n";
        std::cout << s1Ops.str() << "\n";
        std::cout << "\n\nStrat2:   "  << strat2 << "\n";
        std::cout << s2Ops.str() << "\n";
    #endif
        if (strat1 < strat2)
        {
            return strat1;
        }
        else
        {
            std::cout << "Strat2\n";
            return strat2;
        }
    
    }
    
    
    
    
    int main()
    {
        int s = 25;
        int t = 193;
        std::cout << "s = " << s << "  t = " << t << "\n";
        std::cout << MinMoves(s, t) << std::endl;
    }
    

    【讨论】:

    • (100+1+1+1)*2+1 = 207
    【解决方案4】:

    短 BFS 算法。它找到图中每个顶点 x 连接到 x + 1、x - 1 和 x * 2 的最短路径; O(n)

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int _MAX_DIS = 2020;
    const int _MIN_DIS = 0;
    
    int minMoves(int begin, int end){
        queue<int> Q;
        int dis[_MAX_DIS];
        fill(dis, dis + _MAX_DIS, -1);
    
        dis[begin] = 0;
        Q.push(begin);
    
        while(!Q.empty()){
            int v = Q.front(); Q.pop();
            int tab[] = {v + 1, v - 1, v * 2};
            for(int i = 0; i < 3; i++){
                int w = tab[i];
                if(_MIN_DIS <= w && w <= _MAX_DIS && dis[w] == -1){
                    Q.push(w);
                    dis[w] = dis[v] + 1;
                }
            }
        }
    
        return dis[end];
    }
    
    int main(){
    
        ios_base::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
    
        cout << minMoves(1, 1000);
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-28
      • 1970-01-01
      • 2020-06-12
      • 2020-12-06
      • 1970-01-01
      • 2017-05-05
      • 1970-01-01
      相关资源
      最近更新 更多