![b8c0bc6f83e4e63e4f735d41065c0ef1.png](https://i-blog.csdnimg.cn/blog_migrate/14c639b9761d27434725ed5b95bc0126.jpeg)
一、 运算符
- & 与运算: 两个位都是 1 时,结果才为 1,否则为 0
- | 或运算: 两个位都是 0 时,结果才为 0,否则为 1
- ^ 异或运算: 两个位相同则为 0,不同则为 1
- ~ 取反运算:0 则变为 1,1 则变为 0
- << 左移运算:向左进行移位操作,高位丢弃,低位补 0 (每左移一位相当于乘一次2)
- >> 右移运算:向右进行移位操作,对无符号数,高位补 0,对于有符号数,高位补符号位 (每右移一位相当于除一次2)
二、 应用场景
^ 异或运算性质:
(1)交换律
(2)结合律(即(a^b)^c == a^(b^c))
(3)对于任何数x,都有x^x=0,x^0=x
(4)自反性 A ^ B ^ B = A ^ 0 = A
常见应用:
- 与运算的应用:(1)清零。如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。 (2)取一个数中指定位
- 或运算的应用: 对一个数的某几位补1
- 异或运算的应用: 消去一对重复的数(利用上面), 交换变量等等。
- 取模: 位运算比加减乘除更加高效。 所以在一些底层的组件例如jdk, redis 中经常用位运算,比如用<< ,>>代替乘除, 用& 代替取模操作。
- 状态压缩和位图: 位运算可以用压缩存储和表示信息。 每个二进制位有0|1 两种状态, 那一个int32 可以表示出 32种状态。 比如linux中的文件状态中的read ,write, 和执行权限就可以用三个二进制位表示, 7(111)表示有Read的权限,有Write的权限和执行的权限。
三、 例题
1、 不使用中间变量交换两数
// 思路: 利用上面提到的异或的结合率 和自反率
// a = (a ^ b) ^ b , b = a ^ b ^ a
void swap(int &a, int &b) {
a ^= b;
b ^= a;
a ^= b;
}
2、 数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的数 (leetcode - 136)
public int singleNumber(int[] nums) {
int res = 0;
for(int i = 0; i < nums.length; i++) {
res = res ^ nums[i];
}
return res;
}
3、 用 O(1) 时间检测整数 n 是否是 2 的幂次 (LintCode - 142)
/** 思路: 常用技巧:n&(n-1)消去n 的二进制最后一位的1
* 比如3&(3-1) = (11)&(10) = (10) ,
* 如果是2的幂次, 那二进制形式下只有一位是1, 消去后为0
*/
public boolean checkPowerOf2(int n) {
if(n <= 0) return false;
return (n & (n - 1)) == 0;
}
4、子集列举 (leetcode - 78 medium)
描述:
Given a set of distinct integers, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Input: nums = [1,2,3]
Output:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
思路:(这道题递归做感觉也很容易理解)
注意数组中数字唯一,可以把这道题理解成高中数学中的排列组合问题。每个数字有取或不取两种可能性, n个数有2的n次方个子集。 可以用一个正整数二进制表示的第i位是1还是0,代表集合的第i个数取或者不取。类似下图:
0 000 {}
1 001 {1}
2 010 {2}
3 011 {1,2}
4 100 {3}
5 101 {1,3}
6 110 {2,3}
7 111 {1,2,3}
A:
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size()
int p = 1 << n;
vector<vector<int>> subs(p);
for (int i = 0; i < p; i++) {
for (int j = 0; j < n; j++) {
// 用 i>>j & 1的方式遍历每一位
if ((i >> j) & 1) {
subs[i].push_back(nums[j]);
}
}
}
return subs;
}
5、 位图
描述:有一千万个整数,整数范围在1到1亿之间, 如何快速查找某个整数是否在这1千万个整数中。
思路: 用一个bit 数组来存储这1-1亿之间的数。如果数字存在, 在对应的下标上标1, 1亿个数只需要1亿个bit位。
public class BitMap { // Java 中 char 类型占 16bit,也即是 2 个字节
private char[] bytes;
private int nbits;
public BitMap(int nbits) {
this.nbits = nbits;
this.bytes = new char[nbits/16+1];
}
public void set(int k) {
if (k > nbits) return;
int byteIndex = k / 16;
int bitIndex = k % 16;
bytes[byteIndex] |= (1 << bitIndex);
}
public boolean get(int k) {
if (k > nbits) return false;
int byteIndex = k / 16;
int bitIndex = k % 16;
return (bytes[byteIndex] & (1 << bitIndex)) != 0;
}
}