从源到目标的最小操作数。

时间:2018-07-21 23:29:25

标签: algorithm language-agnostic dynamic-programming

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

允许的操作

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

0 <源,目标<= 1000。

我尝试过幼稚的递归路由(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;

}

关于如何调整解决方案的任何想法?还是解决它的更好方法?

4 个答案:

答案 0 :(得分:1)

如果您以图形遍历的方式考虑解决方案,其中每个节点都是可以产生的中间值,则递归解决方案就像深度优先搜索(DFS)。您必须完全扩展,直到您尝试了该搜索空间“分支”中的所有解决方案,然后再进行其他操作。如果您有无限循环,这意味着即使存在更短的路径,它也将永远不会终止,即使您没有无限循环,您仍然必须搜索其余解决方案空间以确保其最优。 / p>

请考虑使用类似于广度优先搜索(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))

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

可以建设性地解决此问题。首先,简单的案例。如果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

vs:

((((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 < / p>

通常,如果x比目标值低x,则如果x的二进制表示在第n个位置为1,则需要在最后一个n倍之前放置+1

类似地,如果我们比目标高x,我们同样会使用-1

此过程对于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;
}

答案 3 :(得分:0)

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