编程:将二进制数转换为零所需的最少步骤

时间:2019-02-21 17:29:19

标签: java algorithm bit-manipulation

当时正在从事编程工作,并且一直在寻找正确的算法。这是问题所在:

  

给出一个十进制数字,如果提供了十进制数字,将其转换为零则需要多少个最小可能的步骤:      

      
  1. 如果下一个i + 1位为“ 1”,则更改第i位,其余所有其他i + 2位,之后为0。
  2.   
  3. 无限制地更改最后一位
  4.   

例如:
如果输入为(8)Base10 =(1000)Base2,则采取的步骤为:

1000→1001→1011→1010→1110→1111→1101→1100→0100→0101→0111→0110→0010→0011→0001→0000

总共需要15个步骤。

完成以下定义:

int minStepsRequired(long number)

可以获取伪代码或仅获取算法。这不是作业或作业。

5 个答案:

答案 0 :(得分:7)

这是递归的PHP函数,用于计算所需的步骤数。通过注意以下两个可能的要求来进行操作:

  1. 将字符串转换为0 s(总体要求);和
  2. 将字符串转换为1,然后转换为0 s字符串(以允许翻转前一位)

第二个要求显然是第一个要求的扩展,因此可以编写一个兼有这两个要求的递归函数。对于单位长度的字符串,它有一种特殊情况,只需检查是否需要翻转即可。

function reduce($bits, $value = '0') {
    if (strlen($bits) == 1) {
        // a single bit can be flipped as needed
        return ($bits[0] == $value) ? 0 : 1;
    }
    if ($bits[0] == $value) {
        // nothing to do with this bit, flip the remainder
        return reduce(substr($bits, 1));
    }
    // need to convert balance of string to 1 followed by 0's 
    // then we can flip this bit, and then reduce the new string to 0
    return reduce(substr($bits, 1), '1') + 1 + reduce(str_pad('1', strlen($bits) - 1, '0'));
}

Demo on 3v4l.org

此函数可以适合于存储实际执行的步骤,然后步骤数仅是该数组的计数(-1,因为我们也将原始值也放入了数组中)。要存储步骤,我们需要跟踪字符串的第一部分(以下代码中的$prefix)以及我们要减少的部分:

function reduce($bits, $prefix, $value = '0') {
    if (strlen($bits) == 1) {
        // a single bit can be flipped as needed
        return array($prefix . ($bits[0] == '0' ? '1' : '0'));
    }
    if ($bits[0] == $value) {
        // nothing to do with this bit, flip the remainder
        $prefix .= $bits[0];
        return reduce(substr($bits, 1), $prefix);
    }
    // need to convert balance of string to 1 followed by 0's 
    $prefix .= $bits[0];
    $steps = reduce(substr($bits, 1), $prefix, '1');
    // now we can flip this bit
    $prefix = substr($prefix, 0, -1) . ($bits[0] == '0' ? '1' : '0');
    $steps[] = $prefix . str_pad('1', strlen($bits) - 1, '0');
    // now reduce the new string to 0
    $steps = array_merge($steps, reduce(str_pad('1', strlen($bits) - 1, '0'), $prefix));
    return $steps;
}

您可以这样运行:

$bin = decbin($i);
$steps = array_merge(array($bin), reduce($bin, ''));
echo "$i ($bin) takes " . (count($steps) - 1) . " steps\n";
print_r($steps);

输入为8的输出:

8 (1000) takes 15 steps
Array
(
    [0] => 1000
    [1] => 1001
    [2] => 1011
    [3] => 1010
    [4] => 1110
    [5] => 1111
    [6] => 1101
    [7] => 1100
    [8] => 0100
    [9] => 0101
    [10] => 0111
    [11] => 0110
    [12] => 0010
    [13] => 0011
    [14] => 0001
    [15] => 0000
)

Demo on 3v4l.org

灰色代码

通过查看这些步骤,我们可以看到这实际上是一个Gray code(反射二进制代码),从原始值向下计数到0。因此,如果我们生成足以覆盖起始值的代码列表,可以简单地在该列表中查找起始值的二进制表示形式,这将使我们获得返回到0所需的步骤数:

function gray_code($bits) {
    if ($bits == 1) {
        return array('0', '1');
    }
    else {
        $codes = gray_code($bits - 1);
        return array_merge(array_map(function ($v) { return '0' . $v; }, $codes),
                           array_map(function ($v) { return '1' . $v; }, array_reverse($codes))
        );
    }
}

$value = 8;
$bin = decbin($value);
// get sufficient gray codes to cover the input
$gray_codes = gray_code(strlen($bin));
$codes = array_flip($gray_codes);
echo "$bin takes {$codes[$bin]} steps to reduce to 0\n";
// echo the steps
for ($i = $codes[$bin]; $i >= 0; $i--) {
    echo $gray_codes[$i] . PHP_EOL;
}

Demo on 3v4l.org

如果不需要单独的步骤,则可以使用格雷代码二进制转换器来查找步骤数。这是超快的:

function gray_to_binary($value) {
    $dec = $value;
    for ($i = 1; $i < strlen($value); $i++) {
        $dec[$i] = (int)$dec[$i-1] ^ (int)$value[$i];
    }
    return $dec;
}

echo bindec(gray_to_binary(decbin(115)));

输出:

93

Demo on 3v4l.org

格雷码生成器

我们可以使用迭代格雷代码生成器从原始代码开始倒数。这样做的好处是它不占用任何内存来存储代码,因此可以处理非常大的数目。此版本使用格雷码二进制转换器,该整数转换器适用于整数而不是字符串,如上面的操作:

function gray_to_binary($value) {
    $dec = 0;
    $bits = floor(log($value, 2));
    for ($i = $bits; $i >= 0; $i--) {
        $dec = $dec | (((($dec >> ($i + 1)) ^ ($value >> $i)) & 1) << $i);
    }
    return $dec;
}

function iterate_gray($value) {
    // get the equivalent starting binary value
    $code = decbin($value);
    yield $code;
    $len = strlen($code);
    $count = gray_to_binary($value);
    while ($count > 0) {
        // flip the bit which corresponds to the least significant 1 bit in $count
        $xor = 1;
        while (($count & $xor) == 0) $xor <<= 1;
        $value ^= $xor;
        yield sprintf("%0{$len}b", $value);
        $count--;
    }
}

foreach (iterate_gray(8) as $code) {
    echo $code . PHP_EOL;
}

输出:

1000
1001
1011
1010
1110
1111
1101
1100
0100
0101
0111
0110
0010
0011
0001
0000

Demo on 3v4l.org

答案 1 :(得分:4)

对于递归算法,这是一个很棒的问题。

如果二进制表示的长度为0,则您已经知道答案了。或者,如果不允许长度为0,那么如果长度为1,则根据该位是0还是1来告诉答案。

如果长度大于1:

  • 如果第一位为0,则答案与没有该0位时的答案相同。删除它并递归调用以获得答案。
  • 如果第一位为1,则分为三个子问题,并为每个子问题找到步数:
    1. 建立一种情况,允许您将前导1更改为0。这意味着应该在其后跟1,然后全为0。为此编写一个递归辅助算法。这将与主要算法非常相似,并且可能它们可以共享一些逻辑。
    2. 将1翻转到0(1步)
    3. 将其余位转换为0。另一个递归调用。

该算法可能需要很长时间。它实际上是在计算步数,因此所花费的时间与步数成正比,我认为这大致与输入数成正比。您的方法使用一个long参数,但是对于大的long值,如果使用我的算法,它可能不会在正在运行的计算机的生命周期内终止。同样,步数可能会使int甚至long溢出(如果输入为负long值)。

编码愉快。如果那是我,我将实际进行编码并运行它以验证我是否正确。

快捷方式

以下解决方案不需要递归并且可以持续运行。我无法正确解释它是如何工作的,如果我们想将其用于某些方面,这是一个严重的问题。我玩了一些示例,看到了一个模式并对其进行了概括。相比之下,恕我直言,上述递归解决方案的某些优点在于它易于理解(如果您了解递归的话)。

示例:输入8或1000二进制。结果15或1111二进制。模式是:结果的每个位是结果的前一个位与输入中相同位置的位的异或。因此,可以从1000复制前一位1。下一位是1 XOR 0 = 1,其中1是结果的前一位,并且从输入中获取0。其余两位的计算方法相同。

更长的示例,因此您可以检查自己是否理解:

Input:  115 = 1110011
Result:       1011101 = 93

或在代码中

static BigInteger calculateStepsRequired(long number) {
    // Take sign bit
    int bit = number < 0 ? 1 : 0;
    BigInteger result = BigInteger.valueOf(bit);
    for (int i = 0; i < 63; i++) {
        number = number << 1;
        int sign = number < 0 ? 1 : 0;
        bit = (bit + sign) % 2;
        result = result.shiftLeft(1).add(BigInteger.valueOf(bit));
    }
    return result;
}

我已经根据我自己对上面第一个算法的实现(使用多达1亿个输入)进行了检查,他们一直都同意,因此我相信快速方法也是正确的。

答案 2 :(得分:2)

这是Ole的“快速方法”算法的PHP实现。想法是一样的:

  • 用数字的第一位(从左开始)初始化结果
  • 对于数字的每个后续位,将其与结果的前一位进行异或,从而为结果提供新的位
function getBit($number, $i)
{
    // Extracts bit i from number
    return ($number & (1<<$i)) == 0 ? 0 : 1;
}

function minStepsRequired($number)
{
    $i = 30; // Enough to handle all positive 32-bit integers
    $bit = getBit($number, $i); // First bit
    $res = $bit;
    do
    {
        $i--;
        $bit ^= getBit($number, $i); // Computes XOR between previous bit of the result and current bit of the number
        $res = ($res<<1) + $bit; // Shifts the result to the left by 1 position and adds the new bit
    }
    while($i>0);
    return $res;
}

var_dump(minStepsRequired(8)); // Outputs 15
var_dump(minStepsRequired(115)); // Outputs 93

答案 3 :(得分:2)

重要的是要注意,可以保留原先的零个k位,而您只从第(k + 1)个位开始,而我们忽略了所有起始位,其值为零并始终致力于增加他们的人数。这是我们的主要目标,因此,我们总是将问题空间缩小为相似但较小的问题空间。现在,假设您的电话号码是

1 b1b2b3 ... bn

您需要确保b1为1且b2,b3,...,bn为0,以便能够将b1之前的位修改为0。在那之后,您知道b2为1并且所有后续位均为0,那么新的目标是在知道所有后续位均为0的情况下将b2更改为0。

您可以使用堆栈来跟踪进度,堆栈可以包含与所处理的位数一样多的元素,始终以其顶部表示您当前的目标。

因此,当您想将第一位清零,然后实现正确的后续位序列时,将为您分配子任务,然后才能更改位,一旦成功,则需要类似地进行操作,但忽略第一位。您可以为步骤编号。

假设有多个

1 ... 0000000

可以以2 ^ n-1的步长归零,要做的总步数等于2 ^ n-1 +达到我们在上面看到的组合所需的步数。我没有检查它是否为2 ^ n-1

答案 4 :(得分:1)

起初,我尝试使用递归深度优先功能(在NodeJS中)解决该问题,但它仅适用于小数-由于10^5之类的输入值会由于堆栈中的递归调用。

因此,我然后尝试查看如何将问题减少为较小的问题的总和,并发现N的步骤数(N为2的幂)是

规则1

  

N * 2-1-

(例如:2的步数是3,32的步数是63,256的步数是511,依此类推)。

然后我找到了与其他任何数字(不是2的幂)的关系,并且由于任何整数是2的不同幂的和(因此为二进制表示),所以我只需要查看该数字是否的步骤也将加起来……但是事实并非如此。但是,我确实发现,我不仅需要将每2次幂的步数相加,还应将

规则2

  

从最高位开始,以另一种方式减去并添加步骤

演示

给出数字 42 (二进制为101010

首先应用第1条规则

1 0 1 0 1 0
^ ^ ^ ^ ^ ^
| | | | | |_           0 steps
| | | | |___  2*2-1 =  3 steps
| | | |_____           0 steps
| | |_______  2*8-1 = 15 steps
| |_________           0 steps
|___________ 2*32-1 = 63 steps

然后,应用第2条规则

63 - 15 + 3 = 51
  

步骤总数为 51