【问题标题】:Minimum number of steps to convert one integer to another将一个整数转换为另一个整数的最小步骤数
【发布时间】:2020-05-31 05:37:27
【问题描述】:

最近我遇到一个问题,给定 2 个整数 A 和 B,我们需要以最少的步数将 A 转换为 B。 我们可以对A进行如下操作:

  • 如果 A 是奇数,则减 1
  • 如果 A 是偶数,则增加 1
  • 将 A(偶数或奇数)乘以 2
  • 如果 A 是偶数,则除以 2

同样,我们必须找到将 A 转换为 B 所需的最小步骤数。

约束是0

我的方法: 我尝试使用广度优先搜索来解决问题,将步骤中所有可能到达的数字添加到队列中,但它在更高的约束条件下失败,即超时。

谁能提出更快的替代方案?

编辑:A 不一定小于 B

【问题讨论】:

  • 想想二进制中的数字,想想这些操作对数字的二进制表示做了什么。
  • ...并且只使用一种编程语言来解决。
  • 您能否为这个问题提供任何参考链接
  • @deHaar 抱歉,为什么我不能使用多个?我知道所有 3 个,无论哪种实现更容易,我都会去做。
  • 认为标签是 AND 不是 OR。您实际上要求使用 Java、Python 和 C++ 编写的程序。不是你想要的。

标签: python algorithm bit-manipulation bitwise-operators


【解决方案1】:

基本上你有以下操作:

  1. 翻转最低位
  2. 向左或向右移动位

假设你有A == 0,你将如何构造B?对了,你把低位一个一个翻转,然后把数字左移,比如B == 5,就是0x0101,你就需要2次翻转和2次移位。

现在,我们必须处理A != 0 时的情况——在这种情况下,您必须将低位转为0 并右移以清理混乱。例如,如果你有A == 32,即0x0100000,你想得到5(0x0101),你必须向右移动3次,然后翻转低位就完成了。

所以,你所要做的就是:

  1. 计算在 A 的最高位与 B 的最高位相等之前,您必须执行多少次翻转/r 移位。
  2. 然后计算需要多少次翻转/r-shift 来清理其余部分
  3. 计算重建 B 的下部需要多少次翻转/左移。

好的,几个小时过去了,这就是解决方案。首先是一个有用的函数,它表示我们需要多少个操作来创建一个数字:

def bit_count(num) :
    # the number of non-zero bits in a number
    return bin(num).count('1')

def num_ops(num) :
    # number of shifts + number of flips
    return num.bit_length() + bit_count(num)

现在,假设 A > B,否则我们可以在保持操作数量相同的情况下交换它们。这是我们必须将A 移动多远才能使其从与B 相同的位开始:

needed_shifts = A.bit_length() - B.bit_length()

同时我们需要翻转一些位:

mask = (1 << (needed_shifts+1)) - 1
needed_flips = bit_count(A & mask)

现在我们计算清理A 和重建B 需要多少操作:

A >>= needed_shifts
clean_shifts = (A & ~B).bit_length()
clean_flips = bit_count(A & ~B)
rebuild_shifts = (B & ~A).bit_length()
rebuild.flips = bit_count(B & ~A)

最后我们一起总结一下:

result_ops = needed_shifts + needed_flips + max(clean_shifts,rebuild_shifts) * 2 + clean_flips + rebuils_flips

就是这样,伙计们! =)

【讨论】:

  • @sweetsecret 用解决方案代码(python)更新了答案
  • 恐怕 clean Arebuild B 所需的步骤数计算错误:它只取决于低A 的顺序位,不管B 的对应位如何,同样rebuild_ops 应该只依赖于B 的低位。例如,将 0b101 转换为 0b111 应该采用 0 + 1 + num_ops(0b000) + num_ops(0b010) -> 4 但我不知道如何改进这 5 步解决方案:dec, shr, inc, shl, inc。
  • @chqrlie 我同意错误计算的步骤,但我的错误有点不同。你所说的“不管B的对应位如何”有点不对劲。假设 A=0x1011 1101 和 B=0x1011 0010,我们可以有 A&amp;~B=0x0000 1101,我们只需要重新生成低 4 位,因为高 4 位是相同的。
  • @chqrlie 无论如何,也许我们在谈论同一件事。我稍微修改了代码,现在翻转是单独计算的,但是移位是max(A,B)*2,因为无论是否有位,我们都必须双向移动,所以我们取最大值并乘以 2。
  • 我觉得这也是一个正确的做法,非常感谢。
【解决方案2】:

可用操作列表是对称的:2 组操作,每组相反:

  • 最后一位可以翻转
  • 如果低位为0,数字可以左移一位或右移一位。

因此从AB 或从BA 需要相同数量的操作。

AB 最多需要从A0 的操作数加上从B0 的操作数。这些操作严格减少了AB 的值。如果一路上可以从AB 达到中间值,则无需一直到0

这是一个简单的函数,它在 AB 上执行各个步骤,并在找到此公共数字后立即停止:

def num_ops(a, b):
    # compute the number of ops to transform a into b
    # by symmetry, the same number of ops is needed to transform b into a
    count = 0
    while a != b:
        if a > b:
            if (a & 1) != 0:
                a -= 1
            else:
                a >>= 1
        else:
            if (b & 1) != 0:
                b -= 1
            else:
                b >>= 1
        count += 1
    return count

【讨论】:

  • 此解决方案不正确。例如 a=3,b=1。解应该是 1,但是这个函数返回 2。
  • @trincot:你如何一步一步从31? A 是奇数,所以你必须先递减 A,然后再右移。
  • 你是对的,我错过了A为奇数时不允许除法的条件。
【解决方案3】:

这个问题可以使用动态规划来优化。

我写了以下代码,考虑了一些事情:

  1. 应通过设置基本条件来小心避免无限递归。例如:如果 A=0 且 B

  2. 如果函数 convert(A, B) 被调用超过 1 次进入递归并且之前未计算状态 (A, B) 的答案,则递归终止,因为这种情况下不存在答案。例如:(80, 100) -> (160, 100) -> (80->100) -> (160, 100) -> ........

这是通过将每个状态的计数保存到映射中并为 DP 的相同状态定义最大递归调用限制(以下程序中为 3)来完成的。

映射dp 维护每个状态(A,B)的答案,映射iterationsCount 维护调用同一状态(A, B) 的次数。

看看下面的实现:

#include <utility> 
#include <iterator> 
#include <map>
#include <set> 
#include <iostream>
#include <climits>

typedef long long int LL;

std::map<std::pair<LL, LL>, LL> dp;

std::map<std::pair<LL, LL>, int > iterationsCount;

LL IMPOSSIBLE = (LL)1e9;

LL MAX_RECURSION_LIMIT = 3;

LL convert(LL a, LL b) 
{ 
//std::cout<<a<<" "<<b<<std::endl;

    // To avoid infinite recursion:
        if(iterationsCount.find(std::make_pair(a, b))!=iterationsCount.end() &&
        iterationsCount[std::make_pair(a,b)] > MAX_RECURSION_LIMIT &&
        dp.find(std::make_pair(a,b))==dp.end()){
            return IMPOSSIBLE;
        }

    // Maintaining count of each state(A, B)
        iterationsCount[std::make_pair(a, b)]++;

    LL value1, value2, value3, value4, value5;

    value1 = value2 = value3 = value4 = value5 = IMPOSSIBLE;

    if(dp.find(std::make_pair(a,b)) != dp.end()){
        return dp[std::make_pair(a, b)];
    }

    // Base Case 
    if(a==0 && b<0){
        return IMPOSSIBLE;
    }

    // Base Case 
    if (a == b) 
        return 0; 

    //Conditions
    if (a%2 == 1){

        if(a < b){

            value1 = 1 + convert(2*a, b);

        }

        else if(a > b){

            value2 = 1 + convert(a-1, b); 

        }
    }
    else{

        if(a < b){


            value3 = 1 + convert(a*2, b);



            value4 = 1 + convert(a+1, b);

        }

        else if(a > b){

            value5 = 1 + convert(a/2, b);

        }
    }

    LL ans = std::min(value1, std::min(value2, std::min(value3, std::min(value4, value5))));

    dp[std::make_pair(a, b)] = ans;

    return ans;
} 

int main(){

    LL ans = convert(10, 95);
    if(ans == IMPOSSIBLE){
        std::cout<<"Impossible";
    }else{
        std::cout<<ans;
    }
    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-22
    • 1970-01-01
    • 1970-01-01
    • 2014-06-18
    相关资源
    最近更新 更多