1、位运算
程序中的所有数在计算机内存中都是以二进制的形式储存的。
位运算就是直接对整数在内存中的二进制位进行操作。
比如,and运算本来是一个逻辑运算符,但整数与整数之间也可以进行and运算。
举个例子,6的二进制是110,11的二进制是1011,那么6 and 11的结果就是2,它是二进制对应位进行逻辑运算的结果.
&运算时,当且仅当两者都为 1 时,结果才为 1.
使用二进制来表示就是如下:
6 :0 1 1 0
11:1 0 1 1
——————
2 :0 0 1 0
相比于使用常规的运算符,在计算机语言中,合理使用位运算符能很好地帮助我们提高效率。
2.1、位运算符
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 非 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
2.2、实例
2.2.1、与
1 | 6 :0 1 1 0 |
当且仅当两者都为 1 的时候,结果才为 1,所以在这里只有第三位两者都为 1 ,所以结果就是 0010。
2.2.2、或
1 | 6 :0 1 1 0 |
当且仅当两者都为 0 的时候,结果才为 0,所以在这里所有的结果都为 1 ,所以结果就是 1111。
2.2.3、非
1 | 6 :0 1 1 0 |
非的规则是将 1 变成 0,0 变成 1,所以0110非操作之后就是1001,为9.
2.2.4、异或
1 | 6 :0 1 1 0 |
异或的运算规则:0^0=0 0^1=1 1^0=1 1^1=0 相同为 0,相异为 1.
2.2.5、左移和右移
左移:
1 | 6 :0 1 1 0 |
左移就是将所有的 1 都左移一位,然后空位上补0,所以 6 左移一位的结果就是 12.相当于 6 乘 2 等于 12.
右移:
1 | 6 :0 1 1 0 |
右移就是将所有的 1 都右移一位,然后空位上补0,所以 6 右移一位的结果就是 3.相当于 6 除 2 等于 3.
从上面可以看出,如果我们在平时需要用到乘法或者除法,就可以使用左移和右移来进行替代,可以更好地提高效率。
2.3、使用技巧
2.3.1、实现乘除法
数 a 向右移一位,相当于将 a 除以 2;数 a 向左移一位,相当于将 a 乘以 2
1 | int a = 2; |
2.3.2、交换两个数
使用位运算交换两个数的时候,可以不使用第三个变量,虽然这个操作普通的运算也能做到,但是效率远不如位运算。
1 | //普通操作 |
2.3.3、判断奇偶数
只要根据数的最后一位是 0 还是 1 来决定即可,为 0 就是偶数,为 1 就是奇数。
1 | if(0 == (a & 1)) { |
2.3.4、交换符号
交换符号将正数变成负数,负数变成正数
1 | int reversal(int a) { |
2.3.5、求绝对值
整数的绝对值是其本身,负数的绝对值正好可以对其进行取反加一求得,即我们首先判断其符号位(整数右移 31 位得到 0,负数右移 31 位得到 -1,即 0xffffffff),然后根据符号进行相应的操作
1 | int abs(int a) { |
上面的操作可以进行优化,可以将 i == 0 的条件判断语句去掉。我们都知道符号位 i 只有两种情况,即 i = 0 为正,i = -1 为负。对于任何数与 0 异或都会保持不变,与 -1 即 0xffffffff 进行异或就相当于对此数进行取反,因此可以将上面三目元算符转换为((a^i)-i),即整数时 a 与 0 异或得到本身,再减去 0,负数时与 0xffffffff 异或将 a 进行取反,然后在加上 1,即减去 i(i =-1)
1 | int abs2(int a) { |
除此之外,还有很多用途。
2.4、写法
和普通的运算符一样,位运算符也可以组成复合运算符的形式。
如下:
1 | &= 例:a&=b 相当于 a=a&b |
2.5、算法题应用
2.5.1、只出现一次的数字
问题描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
分析
- 0和任意数字进行异或操作结果为数字本身.
- 两个相同的数字进行异或的结果为0.
解答
1 | class Solution { |
2.5.2、只出现一次的数字
问题描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
分析
这题和上一题的思路略有不同,这题其他数字出现了3次,那么我们如果直接使用位运算异或操作的话无法直接找到结果,就需要巧妙的运用二进制的其他特性:判断整除求余操作。即判断所有数字二进制1的总个数和0的总个数一定有一个不是三的整数倍,如果0不是三的整数倍那么就说明结果的该位二进制数字为0,同理否则为1.
在具体的操作实现上,问题中给出数组中的数据在int范围之内,那么我们就可以在实现上可以对int的32个位每个位进行依次判断该位1的个数求余3后是否为1,如果为1说明结果该位二进制为1可以将结果加上去。最终得到的值即为答案。
解答
1 | class Solution { |