《图解数据结构算法》位运算总结笔记
总记
位运算,即“逐位运算”,指通过将数值转化为二进制的形式,再通过对二进制数位特性的分析解决实际问题的解题办法。本文通过分析LeetCode平台上《图解数据结构算法》一文中的位运算专题来介绍常见的位运算解题技巧。
方法总览
遍历
遍历一个十进制数的所有二进制数位是位运算中常见的操作,代码如下:
1 | int result = 0; |
这种遍历方法在实际情况中多用于计算数的汉明重量。
模拟加法
实际上,我们可以通过模拟加法器的工作原理,来用位运算进行加减运算。
每个int类型的二进制信息由32个bit位储存,其中首位为符号位,其它31个数位则负责储存这个整数,我们容易知道,对于两个二进制数位相加,可能出现的情况如下表。
数位1 | 数位2 | 新的数位结果 | 进位 |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
所以,基于加法原则,我们可以模拟加法器的行为写出加法器的逻辑代码。
1 | public int EncryptionCalculate(int dataA, int dataB) { |
减法运算也同理,即为正数和负数之间相加,不过我们也应当同时搞懂负数在计算机中的储存方式。
反码:将某个数的所有二进制数位全部反转(即0变为1,1变为0)
补码:某个数的反码+1
可以论证,根据上述加法器运算原则,一个数的原码加上其补码一定等于0(从右到左最后一位数最终会在左移中溢出)
所以,为了方便运算,计算机储存负数的方法是储存一个数的补码,这样可以保证一个数和其相反数的和必定为0。
同时,我们可以知道,由于一个数的最高数位是符号位,所以作为正数(或0),其符号位一定为0;作为负数,根据补码原理推定,其符号位一定为1,这通常是我们判断一个数是否为负的依据。
综上所述,将一个数和另一个数的补码进行加法运算,即为减法的工作原理。
异或除重
异或运算是指对两个数进行逐位操作并返回一个具有如下性质的数:对于两个数的同一数位而言,如果它们的两个数位相同,则记该数该位为0;否则记该数该位为1。这种运算的真值表如下:
数位1 | 数位2 | 新的数位结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
容易知道,对于任意一个数x
,规定异或运算符为^
,则一定有x ^ x == 0
成立,所以如果要找出在成对数据中单独出现的某个数,可以遍历数组并逐个异或,根据异或运算的交换律,最后剩下的数必定为那个单独的数。
异或除重运算的更普遍情况
在本书中,问题训练计划6的情况则更加复杂,一组数据流中只有一个数出现了一次,其他所有数都各自分别出现了三次,这样直接使用异或运算就没有用处了。
对此,我们可以推广异或运算。如果我们把视角固定在某一个具体的数位上,对数组中所有数进行分析,则该数位上的“1”总出现次数必定是3的整数倍或3的整数倍余1。这是因为除了一个数以外,所有的其他数都以三个一组出现。
所以,逐个统计某数位上1的出现次数,然后对3求余数,即可得出那个单独出现的数的情况,代码如下。
1 | public class Solution { |
可以知道,异或运算是以2取余的上述方法的特殊情况。
后记
位运算的操作非常具有潜力,在特定情况下巧妙运用位运算可以提升运行效率,节省空间。