【算法】位运算技巧

对于仍然不太清楚位操作符的同学们,可以看看这篇文章:位操作符

特别注意

特别注意:使用按位操作符时要注意,相等(==)与不相等(!=)的优先级在按位运算符之上!!!!
这意味着,位运算符的优先级极小,所以使用位运算符时,最好加上括号()

重要技巧

基本的操作我就直接略过了。下面是我认为必须掌握技巧:(注意,我把一些生僻的技巧都已经砍掉了,留下来的,就是我认为应该会的)

使用 x & 1 == 1 判断奇偶数。(注意,一些编辑器底层会把用%判断奇偶数的代码,自动优化成位运算)

不使用第三个数,交换两个数。x = x ^ y , y = x ^ y , x = x ^ y。(早些年喜欢问到,现在如果谁再问,大家会觉得很low)

两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身。(对于找数这块,异或往往有一些别样的用处。)

x & (x - 1) ,可以将最右边的 1 设置为 0。(这个技巧可以用来检测 2的幂,或者检测一个整数二进制中 1 的个数,又或者别人问你一个数变成另一个数其中改变了多少个bit位,统统都是它)

i+(~i)=-1,i 取反再与 i 相加,相当于把所有二进制位设为1,其十进制结果为-1。

对于int32而言,使用 n >> 31取得 n 的正负号。并且可以通过 (n ^ (n >> 31)) - (n >> 31) 来得到绝对值。(n为正,n >> 31 的所有位等于0。若n为负数,n >> 31 的所有位等于1,其值等于-1)

使用 (x ^ y) >= 0 来判断符号是否相同。(如果两个数都是正数,则二进制的第一位均为0,x^y=0;如果两个数都是负数,则二进制的第一位均为1;x^y=0 如果两个数符号相反,则二进制的第一位相反,x^y=1。有0的情况例外,^相同得0,不同得1)

“异或”是一个无进位加法,说白了就是把进位砍掉。比如01^01=00。

“与”可以用来获取进位,比如01&01=01,然后再把结果左移一位,就可以获取进位结果。

使用掩码遍历二进制位

这个方法比较直接。我们遍历数字的 32 位。如果某一位是 1 ,将计数器加一。

我们使用 位掩码 来检查数字的第 \(i^{th}\) 位。一开始,掩码 m=1 因为 1 的二进制表示是

\[0000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0001\]
0000 0000 0000 0000 0000 0000 0000 0001

显然,任何数字跟掩码 11 进行逻辑与运算,都可以让我们获得这个数字的最低位。检查下一位时,我们将掩码左移一位。

\[0000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0000\ 0010\]
0000 0000 0000 0000 0000 0000 0000 0010

【算法】位运算技巧

并重复此过程,我们便可依次遍历所有位。

Java

public int hammingWeight(int n) { int bits = 0; // 用来储存 1 的个数 int mask = 1; // 掩码,从最低位开始 for (int i = 0; i < 32; i++) { // 注意这里是不等于0,而不是等于1,因为我们的位数是不断在变化的,可能等于2、4、8... // 使用按位操作符时要注意,相等(==)与不相等(!=)的优先级在按位运算符之上!!!! // 使用按位运算符时,最好加上括号() if ((n & mask) != 0) { bits++; } mask <<= 1; } return bits; }

注意:这里判断 n & mask 的时候,千万不要错写成 (n & mask) == 1,因为这里你对比的是十进制数。(我之前就这么写过,记录一下...)

无符号右移遍历二进制位

逐位判断
根据 与运算 定义,设二进制数字 n ,则有:

\(n \& 1 = 0\),则 n 二进制 最右一位 为 00 ;

\(n \& 1 = 1\),则 n 二进制 最右一位 为 11 。

根据以上特点,考虑以下 循环判断 :

判断 n 最右一位是否为 1 ,根据结果计数。

将 n 右移一位(本题要求把数字 n 看作无符号数,因此使用 无符号右移 操作)。

算法流程:

初始化数量统计变量 res = 0。

循环逐位判断: 当 n = 0 时跳出。

res += n & 1 : 若 \(n \& 1 = 1\) ,则统计数 res 加一。

n >>= 1 : 将二进制数字 n 无符号右移一位( Java 中无符号右移为 ">>>" ) 。

返回统计数量 res。

public class Solution { public int hammingWeight(int n) { int res = 0; while(n != 0) { res += n & 1; // 遍历 n >>>= 1; // 无符号右移 } return res; } } 反转最后一个 1

对于特定的情况,如 只有 1 对我们有用时,我们不需要遍历每一位,我们可以把前面的算法进行优化。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspwpz.html