Brian Kernighan算法介绍:
两个核心操作:n&(-n)和n&(n-1)
①n&(-n)
n & (-n)
这个操作用来获取一个数 (n) 在二进制表示下最低位的1,和它的位位置。
在二进制系统中,负数通常以补码(two’s complement)的形式存储。一个正整数的补码就是它的反码(每个二进制位取反)加1,对应的算式是-n=~n+1。因此,当你做 n & (-n)
时,实际上你将 n 和 n 的补码进行位与操作。
这个操作的效果是,所有 n 最低位1以上的位都会被设置为0,因为 n 和 -n 在这些位置上数值相反。只有最低位的1会在 n 和 -n 中保持一致(因为n的补码是从最低位的1开始取反的),因此这个位与操作返回的结果就是只有这个位是1其他都是0的数值。
例如,给定数字 n = 10(二进制表示为 1010),n & (-n)
的计算方法如下:
- n = 1010
- -n = ~(1010 - 1) + 1 = ~(1001) + 1 = 0110 + 1 = 0111 (即负数n的表示)
- n & (-n) = 1010 & 0111 = 0010
结果 0010 就是数 10 的二进制表示中最低位1所在的位置。
②n&(n-1)
n & (n - 1)
这个操作的作用是将n二进制表示的最低位的1置为0。
让我们详细看看这个操作是如何工作的:
-
二进制表示:每个非负整数 n 都有一个二进制表示,例如,数 13 的二进制表示为 1101。
-
减一操作:当我们从一个数 n 减去 1,则 n 的最右边的 1 会变成 0,并且这个 1 右边的所有 0 都变为 1。所以,如果 n = 13 (1101 in binary),那么 n - 1 = 12 (1100 in binary)。
-
位与操作:位与操作 (&) 只在两个相应的位都为1的时候才返回1。当你做
n & (n - 1)
时,从 n 中减 1 得到的数字会将 n 中最低位的1变为0(因为那个位在 n 中是1,在 n-1 中变成了0),而所有低于这个位的位都是0(因为在 n 中这些位是0,在 n - 1 中是1,0 AND 1 的结果是0),因此这些位的与操作结果还是0。高于最低位的1的位不会变化,因为在两个数中它们保持不变。
让我们使用13这个数来演示 n & (n - 1)
的具体操作:
- n = 1101 (13)
- n - 1 = 1100 (12)
- n & (n - 1) = (1101) & (1100) = 1100
这个操作将最右边的1变成了0,现在的数值是 12。重复这个过程:
- n = 1100 (12)
- n - 1 = 1011 (11)
- n & (n - 1) = (1100) & (1011) = 1000
又一次,将最右边的1变成了0,现在数值是 8。如果一直重复这个操作,最后将会归0。
实例:
①判断一个数是否是2的幂
class Solution {
public:
bool ispow2first(int n) {
return n == (n & -n);
}
bool ispow2second(int n) {
return (n & (n - 1)) == 0;
}
};
这两种实现分别基于两个核心操作:n&(-n)和n&(n-1)
首先,2的幂的二进制表达式除了最高位1以外其余都是0。
2:10,4:100:128:10000000
第一种实现逻辑是如果一个数的二进制内码取最低位1后仍是它本身,那么这个数就是2的幂
第二种实现逻辑是如果一个数的二进制内码删除最低位1后等于0,那么这个数就是2的幂
测试点:
231. 2 的幂 - 力扣(LeetCode)