前言
LeetCode死磕系列五: 位运算
位运算,在性能上是超过加减乘除的,只不过,费些脑细胞。基础的与&
或|
异或^
运算,以及左移动、右移。
常用操作
&、| 、^、>>、<<、>>>
假设整数变量 A 的值为 60 和变量 B 的值为 13:
操作符 | 描述 | 例子 | ||
---|---|---|---|---|
& | 如果相对应位都是1,则结果为1,否则为0 | (A&B),得到12,即0000 1100 | ||
\ | 如果相对应位都是 0,则结果为 0,否则为 1 | (A \ | B)得到61,即 0011 1101 | |
^ | 如果相对应位值相同,则结果为0,否则为1 | (A ^ B)得到49,即 0011 0001 | ||
〜 | 按位取反运算符翻转操作数的每一位,即0变成1,1变成0。 | (〜A)得到-61,即1100 0011 | ||
<< | 按位左移运算符。左操作数按位左移右操作数指定的位数。 | A << 2得到240,即 1111 0000 | ||
>> | 按位右移运算符。左操作数按位右移右操作数指定的位数。 | A >> 2得到15即 1111 | ||
>>> | 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 | A>>>2得到15即0000 1111 |
异或
2 ^ 2 = 0;
与
0 & 1 = 0;
1 & 1 = 0;
n & (n-1)
10000000;
01111111;
<----- binary ---->
n n n-1 n&(n-1)
-- ---- ---- -------
0 0000 0111 0000 *
1 0001 0000 0000 *
2 0010 0001 0000 *
3 0011 0010 0010
4 0100 0011 0000 *
5 0101 0100 0100
6 0110 0101 0100
7 0111 0110 0110
8 1000 0111 0000 *
9 1001 1000 1000
10 1010 1001 1000
11 1011 1010 1010
12 1100 1011 1000
13 1101 1100 1100
14 1110 1101 1100
15 1111 1110 1110
可以看到,其第一个方式是用于判断是否为2的幂次,n&(n-1)
的结果为0的话,则为2的幂次。
从运算结果分析(以12为例):
12二进制表示为:1100
,减去1之后,为1011
,可以发现,减去1之后,是将该数字的二进制的最右边的1改为0,n&(n-1)之后的结果为1000
,比较1100
和1000
,这个操作可以总结为:将当前数字二进制最右边的1
去除。最右边的1
左边部分不变。所以,也可可以用于统计数字的二进制中1的个数问题。
补码:
~x + 1 是 x的补码;
补码
1 + 11111111111111111 = 000000000000000000;
2 + 11111111111111110 = 000000000000000000
(s | 1<<k)
s: 集合(二进制表示包含的元素)
集合元素$s = {1,4,6}$ 则 $s = 1010010{2} = 82{10}$
k: 元素
将元素
k
放入集合s
中
举例说明
当前$s=82$, 此时$k=2$,将$k$放入集合$s$中
- $(1<<k) = 4 = 100_{2}$
- $(s | 1<< k) == 1010110{2} == 86{10}$
神奇的操作,适用于压缩状态dp
LeetCode & 剑指offer 位运算题目整理
- 面试题15:二进制中1的个数
- 136 Single Number
LeetCode 位运算题目整理
面试题15:二进制中1的个数
public static int numberOf(int n){
// n & (n - 1) 会消除 n 中最后一位中的 1。
int res = 0;
while (n != 0){
res++;
n &= (n-1);
}
return res;
}
136 Single Number
这题是去除数组中的重复元素(给的测试集是只有一个不重复的元素),用了异或运算,
思路:
相等的数相异或结果为0,0和任何数相异或结果为这个数本身。根据这个特点,可以先设置一个初始值为0,然后分别和数组中的每个元素相异或,最后保留的肯定为唯一一个不重复的值。
相等的元素相异或的值为0,不是特指相等的元素必须相连,也可以不相连。
public singleNumber(int[] nums){
int res = 0;
for( int i : nums){
res ^= i;
}
return res;
}