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

Posted

技术标签:

【中文标题】编程:将二进制数转换为零所需的最少步骤【英文标题】:Programming: Minimum steps required to convert a binary number to zero 【发布时间】:2019-07-15 17:09:20 【问题描述】:

我正在做一个编程练习,一直在寻找正确的算法。问题来了:

给定一个十进制数,将其转换为零所需的最小可能步数:

    如果下一位 i+1 为 '1' 且所有其他位 i+2 及以后的所有位均为 0,则更改位 i 无限制更改最后一位

例如:

如果输入是(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)

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

【问题讨论】:

嗨,roger_that,你也想在here中发布这个谜题 并澄清你的过程,即什么是'set'(= 1?)和'rest'? @Fureeish 那里的人可能认为 *** 是一台自动作业机。不会怪他们。可以骗过任何人。 :) 不。您需要先自己尝试一下。写一些代码,如果你被难住了就回来。 这是一个简单的图的广度优先搜索。节点是二进制值;边缘是每个节点允许的移动。网上有很多这样的搜索例子。编码你的转换,测试你的结果。如果您卡住,请在此处发布。 【参考方案1】:

起初,我尝试使用递归深度优先函数(在 NodeJS 中)来解决它,但它只适用于小数字 - 由于递归调用的数量,10^5 等输入值会产生运行时错误在堆栈中。

然后我试图看看如何将问题简化为较小问题的总和,并发现 N 的步骤数,即 N 的 2 次方,是

规则 #1

N * 2 - 1

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

然后我找到了如何处理任何其他数字(不是 2 的幂),并且由于任何整数都是 2 的不同幂的总和(因此是二进制表示),我只需要查看数字是否步骤也会加起来......但事实并非如此。但是,我确实发现我不仅要添加每一个 2 的幂的步数,还要添加

规则 #2

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

演示

给定数字 42101010 二进制)

让我们首先应用规则#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

实现 (javascript)

function minOperations(n) 
  const bin = n.toString(2);
  const digitCount = bin.length;
  let accumulator = 0;
  let sign = 1;

  for (let i = 0; i < digitCount; ++i) 
    const digit = Number.parseInt(bin.charAt(i));
    const power = digit > 0 ? Math.pow(2, digitCount - (i + 1)) : 0;
    const steps = digit * (power * 2 - 1);

    accumulator += steps * sign;
    sign = sign * (digit == 0 ? 1 : -1);
  
  
  return accumulator;

【讨论】:

发生这种情况的原因是在每个步骤中,您基本上都在否定整个计算的符号。因此,您可以将其视为 63 - (15 - 3) = 51,而不是像在加法和减法之间交替那样考虑它。因此,正如其他人指出的那样,这是递归解决方案的一个大问题。 正如我在文章开头提到的,递归不适用于大数字,因此这里解释了解决方法【参考方案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

【讨论】:

能解释一下步骤吗? @Developerium 我添加了更多细节。【参考方案3】:

重要的是要注意前 k 位,即 0,可以保持原样,您只从第 (k + 1) 位开始,我们忽略所有起始位,其值为 0并始终致力于增加他们的人数。这是我们的主要目标,因此,我们总是将问题空间缩小到类似但更小的问题空间。现在,假设你的号码看起来像

1b1b2b3...bn

您需要确保 b1 为 1 且 b2, b3, ..., bn 为 0,以便能够将 b1 之前的位修改为 0。在此之后,您知道 b2 为 1 并且后面的所有位都是0,那么新的目标就是把b2改成0,知道后面的所有位都是0。

您可以使用堆栈跟踪您的进度,该堆栈可以包含与您正在处理的位一样多的元素,始终其顶部将代表您当前的目标。

因此,当您想将第一位归零时,在您更改位之前,获得正确的后续位序列代表您的子任务,一旦成功,您需要继续进行类似的操作,但忽略第一位.你可以给步骤编号。

假设有多个

1...0000000

可以在 2^n - 1 步内归零,总步数等于 2^n - 1 + 实现我们上面看到的组合所需的步数。我没有检查它是否是 2^n - 1 虽然

【讨论】:

【参考方案4】:

这是一个用于计算所需步数的递归 PHP 函数。它通过注意到有两个可能的要求来运作:

    将字符串转换为0s(总体要求);和 将字符串转换为1,后跟0s 字符串(以允许翻转前面的数字)

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

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(Reflected Binary 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$lenb", $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

【讨论】:

很好的答案。感谢this site,我还发现了与格雷码的联系(你给它一个序列的开头,它会告诉你它是什么)。 @Olivier 谢谢。我以 EE 的身份开始生活,不得不为异步接口设计其中一个,因此在我开始编写代码后就认出了这种模式。您提供的那个网站的链接看起来非常有用,谢谢 - 我会将它添加到我的参考列表中。

以上是关于编程:将二进制数转换为零所需的最少步骤的主要内容,如果未能解决你的问题,请参考以下文章

数制转换

使用朴素贝叶斯模型获得良好结果所需的最少特征数是多少?

使序列的所有元素为 0 所需的最小步骤数

双字节距离显示一半的值

Vijos 回文数

如何计算将字符串转换为回文所需的字符数?