【算法题解】1342. 将数字变成 0 的操作次数

203 阅读4分钟

1342. 将数字变成 0 的操作次数

题目 leetcode.cn/

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1

示例

示例 1:

输入:num = 14
输出:6

解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 2) 7 是奇数,减 1 得到 6 。
步骤 3) 6 是偶数,除以 2 得到 3 。
步骤 4) 3 是奇数,减 1 得到 2 。
步骤 5) 2 是偶数,除以 2 得到 1 。
步骤 6) 1 是奇数,减 1 得到 0 。

示例 2:

输入:num = 8
输出:4

解释:
步骤 1) 8 是偶数,除以 2 得到 4 。
步骤 2) 4 是偶数,除以 2 得到 2 。
步骤 3) 2 是偶数,除以 2 得到 1 。
步骤 4) 1 是奇数,减 1 得到 0 。

示例 3:

输入:num = 123
输出:12

提示

0 <= num <= 10^6

题解

第一版

function numberOfSteps(num) {
  let steps = 0;
  while (num > 0) {
    if (num % 2 === 0) {
      num /= 2;
    } else {
      num -= 1;
    }
    steps++;
  }
  return steps; 
}

思路与算法

使用了一个 while 循环,每次检查 num 是否大于 0。如果 num 是偶数,就将其除以 2;否则,将其减去 1。每执行一次循环,步数加 1。最终返回的是需要的步数。

在这个实现中,由于输入参数 num 是一个非负整数,因此不需要在循环中检查 num 是否小于 0。如果输入的 num 是负数,那么循环将不会结束,因为 num 永远不会等于 0

第二版

function numberOfSteps(num) {
  let steps = 0;
  while (num > 0) {
    num = (num & 1) === 0 ? num >> 1 : num - 1;
    steps++;
  }
  return steps;
}

思路与算法

在这个函数中,使用了三种位运算符:&~>>

& 用于检查最低位是否为 1,这相当于原函数中的 % 2 操作。如果最低位是 1,就说明 num 是奇数,就需要用位运算 & ~1 把最低位清零,相当于原函数中的 - 1 操作。

~1 的二进制表示为全 1 的补码,和一个二进制数进行按位与操作时,可以把这个二进制数的最低位清零。

>> 用于把一个数除以 2,这相当于原函数中的 / 2 操作。右移一位相当于除以 2,因为右移一位就相当于把二进制数的每一位都向右移动一位,最高位补 0

使用位运算可以使代码更加简洁高效,因为位运算是计算机中最基本的操作之一,它们可以在底层硬件上执行,比算术运算符(如除法和取模)更快。

第三版

function numberOfSteps(num) {
  if (num === 0) {
    return 0;
  }
  return length(num) - 1 + count(num);
}

function length(num) {
  let clz = 0;
  if ((num >> 16) === 0) {
    clz += 16;
    num <<= 16;
  }
  if ((num >> 24) === 0) {
    clz += 8;
    num <<= 8;
  }
  if ((num >> 28) === 0) {
    clz += 4;
    num <<= 4;
  }
  if ((num >> 30) === 0) {
    clz += 2;
    num <<= 2;
  }
  if ((num >> 31) === 0) {
    clz += 1;
  }
  return 32 - clz;
}

function count(num) {
  num = (num & 0x55555555) + ((num >> 1) & 0x55555555);
  num = (num & 0x33333333) + ((num >> 2) & 0x33333333);
  num = (num & 0x0F0F0F0F) + ((num >> 4) & 0x0F0F0F0F);
  num = (num & 0x00FF00FF) + ((num >> 8) & 0x00FF00FF);
  num = (num & 0x0000FFFF) + ((num >> 16) & 0x0000FFFF);
  return num;
}

思路与算法

位运算吓到我了。尤其是官解2,这是个鬼故事。

由上述方案实现步骤可知,当 num > 0 时,总操作次数等于总减 1 的操作数与总除以 2 的操作数之和。总减 1 的操作数等于 num 二进制位 1 的个数,总除以 2 的操作数等于 num 二进制数长度减 1,即最高位右移到最低位的距离。

二进制数长度 len 可以通过前导零数目 clz 间接求解,即 len = W - clz,其中 W = 32 整型的位数。

使用二分法加速求解前导零数目,算法如下:

首先判断 num 前半部分是否全为零,如果是,则将 clz 加上前半部分的长度,然后将后半部分作为处理对象,否则将前半部分作为处理对象。重复以上操作直到处理的对象长度为 1,直接判断是否有零,有则将 clz1

使用分治法来加速求解二进制数位 1 的个数,算法如下:

对二进制数 num,它的位 1 的个数等于所有位的值相加的结果,比如 10110101(2) = 1 + 0 + 1 + 1 + 0 + 1 + 0 + 1。可以将 8 个位的求和分解成 4 个相邻的位的求和,然后将 4 个中间结果分解成 2 个相邻的求和,即 10110101(2) = (1 + 0) + (1 + 1) + (0 + 1) + (0 + 1) = ((1 + 0) + (1 + 1)) + ((0 + 1) + (0 + 1)) = 532 位数的求解过程同理。