Leetcode
1342. 将数字变成 0 的操作次数
给你一个非负整数 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
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/number-of-steps-to-reduce-a-number-to-zero
class Solution {
public:
int numberOfSteps(int num) {
int ret = 0; // 初始化步骤数
while (num) {
// 当 num 不为 0 时
ret += (num > 1 ? 1 : 0) + (num & 0x01); // 计算步骤数
num >>= 1; // 将 num 右移一位
}
return ret; // 返回总步骤数
}
};
这段C++代码是实现了一个名为Solution
的类,并在其中定义了一个numberOfSteps
的成员函数。这个函数接收一个整数num
作为输入参数,然后计算将其变为0所需的操作次数。在每个操作中,根据num
的奇偶性,可以执行以下两种操作之一:
- 如果
num
是偶数,则将其除以2; - 如果
num
是奇数,则将其减去1。
现在我们来详细解释这段代码的关键部分:
ret += (num > 1 ? 1 : 0) + (num & 0x01);
这一行代码的作用是更新ret
的值,ret
变量用于记录执行的操作次数。这一行代码包含两部分,我们逐一解释。
第一部分:(num > 1 ? 1 : 0)
这是一个三元运算符的使用,它的作用是:如果num
大于1,则返回1;否则返回0。这部分代码的意义在于,在执行操作的过程中,只要num
大于1,那么下一步操作(除以2或者减去1)都会使num
减小。所以在这种情况下,我们需要将操作次数ret
加1。
第二部分:(num & 0x01)
这是一个按位与操作,将num
与二进制数0x01
进行按位与运算。这个操作的目的是检查num
的最低有效位(LSB)是否为1,即判断num
是否为奇数。如果num
是奇数,那么(num & 0x01)
的结果为1,表示我们需要先将奇数减1,再执行除以2的操作。如果num
是偶数,那么(num & 0x01)
的结果为0,表示我们只需要执行除以2的操作。
综上所述,这行代码的作用是:
- 判断
num
是否大于1,如果是,则需要执行一次操作,将ret
加1; - 判断
num
是否为奇数,如果是,则需要执行一次减1操作,将ret
加1。
在这一行代码之后,num
会右移一位(num >>= 1;
),相当于将num
除以2。然后继续循环,直到num
变为0,此时ret
的值就是所需的操作次数。
num=14的详细流程
现在我们来详细解析num = 14
时的计算过程。首先,我们设定一个变量ret = 0
来记录操作次数。
- 第一次循环:
num = 14
(二进制表示为1110
)num > 1
,所以(num > 1 ? 1 : 0)
的结果为1,ret
加1。num
是偶数(最低有效位为0),所以(num & 0x01)
的结果为0,ret
不变。ret = 0 + 1 = 1
- 右移
num
(相当于除以2),得到num = 7
(二进制表示为111
)。
- 第二次循环:
num = 7
(二进制表示为111
)num > 1
,所以(num > 1 ? 1 : 0)
的结果为1,ret
加1。num
是奇数(最低有效位为1),所以(num & 0x01)
的结果为1,ret
加1。ret = 1 + 1 + 1 = 3
- 右移
num
(相当于除以2),得到num = 3
(二进制表示为11
)。
- 第三次循环:
num = 3
(二进制表示为11
)num > 1
,所以(num > 1 ? 1 : 0)
的结果为1,ret
加1。num
是奇数(最低有效位为1),所以(num & 0x01)
的结果为1,ret
加1。ret = 3 + 1 + 1 = 5
- 右移
num
(相当于除以2),得到num = 1
(二进制表示为1
)。
- 第四次循环:
num = 1
(二进制表示为1
)num <= 1
,所以(num > 1 ? 1 : 0)
的结果为0,ret
不变。num
是奇数(最低有效位为1),所以(num & 0x01)
的结果为1,ret
加1。ret = 5 + 1 = 6
- 右移
num
(相当于除以2),得到num = 0
。
由于num
变为0,循环结束,此时ret = 6
。所以,将num = 14
变为0需要执行6个操作。
num >>= 1和 num/2的区别
num >>= 1
和num /= 2
这两个操作都会实现将num
除以2的功能。但在计算过程和底层实现上,它们之间存在一定差别。
- 计算过程:
num >>= 1
:这是一个右移位运算,它将num
的二进制表示向右移动一位,即将每个位的值向右移动一位,高位补0。对于非负整数,右移一位相当于将原数除以2。num /= 2
:这是一个除法运算,它将num
除以2,并将结果赋值给num
。
- 底层实现:
- 位运算(如右移)在计算机硬件层面上执行得更快,因为它只需处理数值的二进制表示。而除法运算相对较慢,因为它需要进行更复杂的计算过程。
- 通常情况下,编译器会自动优化除以2的操作,将其转换为更快的右移运算,但这取决于编译器的实现。
- 结果差异:
- 对于非负整数,
num >>= 1
和num /= 2
的结果相同。 - 对于负整数,由于负数在计算机中采用补码表示,右移操作可能导致不同的结果。在这种情况下,
num >>= 1
和num /= 2
可能产生不同的结果。
- 对于非负整数,
综上所述,num >>= 1
和num /= 2
在处理非负整数时结果相同,但在处理负整数时可能不同。位运算通常更快,但在大多数情况下,这两种操作之间的性能差异可以忽略不计。