位运算
最高位的bit为1的数都是负数,最高位bit为0的数都是正数。
一:位运算符
1:有符号位移
含义 | 运算符 | 示例 |
---|---|---|
左移 | << | 0011 => 0110 |
右移 | >> | 0110 => 0011 |
2:无符号位移(>>>逻辑位移)
正数的原码补码,负数的补码(符号位不变)原码取反+1
算数位移
# <<表示左移,不分正负数,低位补0;
注:以下数据类型默认为byte-8位
左移时不管正负,低位补0
正数:r = 20 << 2
20的二进制补码:0001 0100
向左移动两位后:0101 0000
结果:r = 80
负数:r = -20 << 2
-20 的二进制原码 :1001 0100 符号位 : 0是正数,1是负数
-20 的二进制反码 :1110 1011 反码:符号位不变,其它位取反
-20 的二进制补码 :1110 1100 补码:反码+1
左移两位后的补码:1011 0000 方法1: 补码 -1 再取反,可得原码 方法2:求补码的补码(就是它的原码)
反码:1010 1111
原码:1101 0000
结果:r = -80
---------------------------------------------------------------------------------------------
>>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
注:以下数据类型默认为byte-8位
正数:r = 20 >> 2
20的二进制补码:0001 0100
向右移动两位后:0000 0101
结果:r = 5
负数:r = -20 >> 2
-20 的二进制原码 :1001 0100 负数的原码:对应正数的原码符号位取反
-20 的二进制反码 :1110 1011 符号位不变,其余取反
-20 的二进制补码 :1110 1100 +1
右移两位后的补码:1111 1011
反码:1111 1010
原码:1000 0101
结果:r = -5
----------------------------------------------------------------------------------------------
逻辑位移
>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
正数: r = 20 >>> 2
的结果与 r = 20 >> 2 相同;
负数: r = -20 >>> 2
注:以下数据类型默认为int 32位
-20:源码:10000000 00000000 00000000 00010100
反码:11111111 11111111 11111111 11101011
补码:11111111 11111111 11111111 11101100
右移:00111111 11111111 11111111 11111011
结果:r = 1073741819
java没有<<<
3:基本位运算
含义 | 运算符 | 示例 |
---|---|---|
按位或 | ︳ | 0011 1011 -------> 1011 有1为1 |
按位与 | & | 0011 1011 -------> 0011 全1才为1 |
按位取反 | ~ | 0011 => 1100 |
按位异或(相同为0不同为1) | ^ | 0011 1011 --------> 1000 相同为0不同为1 |
4: XOR -异或 运算
异或:相同为 0,不同为 1。也可用“不进位加法”来理解。
异或操作的一些特点:
假设x = 9 1001
操作 | 示例 |
---|---|
x ^ 0 = x 异或0就是x本身 | 1001 ^ 0000 -------> 1001 异或:相同为0,不同为1 |
x ^ 1s = ~x ==>1s = ~0 | 1001 ^ 1111 1s就是全1 --------> 0110 === ~x |
x ^ (~x) = 1s | 1001 ^ 0110 ---------> 1111 异或:相同为0,不同为1 |
x ^ x = 0 | 1001 ^1001 --------> 0000 异或:相同为0,不同为1 |
c = a ^ b => a ^ c = b, b ^ c = a | a = 9.b=7 a: 1001 b: ^ 0111 c: 1110 ----------------------------------- a: 1001 c: ^1110 0111 ===>b=7 |
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c // 结合律 |
5:指定位置的位运算
一条32位的指令,它的最高位是31位,最低位是0位. 以下默认以0开始计。
操作 | 示例 |
---|---|
1. 将x 最右边的 n 位清零:x& (~0 << n) | 假设x:1111 1111 n=4, ~0 =1,左移动4位==》1111 0000 做&运算 1111 1111 1111 0000 1111 0000 ===》将右边的4位都清零 |
2. 获取x 的第 n 位值(0 或者1): (x >> n) & 1 | 10的二进制为1010,获取第1位是1,第2位是0(从低位到高位,从第0位开始数) 1010 --->n =2,右移动2位 ==》10, &1 10 01 00 ===>第二位是0,右数第3位 |
3. 获取x 的第 n 位的幂值:x& (1 <<n) | x 10: 1010 获取第3位,n==3, 1<<3 ==> 1000 1010&1000 ===>1000 |
4. 仅将第 n 位置为1:x|(1 << n) | 或运算,有1结果就为1. |
5. 仅将第 n 位置为0:x & (~ (1 << n)) | 与运算: 有0结果就为0 |
6. 将x 最高位至第 n 位(含)清零:x& ((1 << n) -1) | x:1010 第3位:n==3: 1<<3 ==> 1000 -1 ==> 0111 再&1010 0010 最高位第三位就清零了 |
7. 将第 n 位至第0 位(含)清零:x& (~ ((1 << (n + 1)) -1)) |
6:实战位运算要点
判断奇偶:
-
x % 2 == 1 —> (x & 1) == 1
-
x % 2 == 0 —> (x & 1) == 0
-
x >> 1 —> x / 2.
即: x = x / 2; —> x = x >> 1;
mid = (left + right) / 2; —> mid = (left + right) >> 1; -
X = X & (X-1) 清零最低位的 1
x: 1010 x-1: &1001 -------------- 1000 将最低位的1清0.
-
X & -X => 得到最低位的 1,其它位置就清零了
最高位的bit为1的数都是负数,最高位bit为0的数都是正数。
x=10: 0000 1010 取反+1 就是负数的补码 -x=-10: 1111 0101 + 1==> 1111 0110 &运算: 0000 1010 & 1111 0110 0000 0010
int x = 10; System.out.println("x:"+Integer.toBinaryString(x)); System.out.println("-x:"+Integer.toBinaryString(-x)); System.out.println(Integer.toBinaryString(x&-x)); console: x:1010 -x:11111111111111111111111111110110 10
-
X & ~X => 0
x: 1010 ~x:0101 & 0000
二:位运算的应用--leetcode
191. 位1的个数
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
public class Solution {
public int hammingWeight(int n) {
int count = 0; //统计
while(n !=0){
count++;
n &= (n-1); //清零最低位的1,清理一次,就统计一次
}
return count;
}
}
231. 2的幂
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
这题的关键是,理解2的N次幂,只有1个1.
方法1:
class Solution {
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
while (n % 2 == 0) n /= 2;
return n == 1;
}
}
如何获取二进制中最右边的 1:x & (-x)。
如何将二进制中最右边的 1 设置为 0:x & (x - 1)。
方法2:
我们通过 x & (-x) 保留了最右边的 1,并将其他位设置为 0 若 x 为 2 的幂,则它的二进制表示中只包含一个 1,则有 x & (-x) = x。
若 x 不是 2 的幂,则在二进制表示中存在其他 1,因此 x & (-x) != x。
因此判断是否为 2 的幂的关键是:判断 x & (-x) == x。
class Solution {
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
long x = (long) n;
return (x & (-x)) == x;
}
}
方法3:
2 的幂二进制表示只含有一个 1。 比如:1 就是1 2:10 4:100 8:1000 都是只有1个1,才是2的N次幂
x & (x - 1) 操作会将 2 的幂设置为 0,因此判断是否为 2 的幂是:判断 x & (x - 1) == 0
class Solution {
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
long x = (long) n;
return (x & (x - 1)) == 0;
}
}
338. 比特位计数
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
输入: 5
输出: [0,1,1,2,1,2]
方法1:
时间复杂度:O(nk)O(nk)。对于每个整数 xx,我们需要 O(k)O(k) 次操作,其中 kk 是 xx 的位数。
空间复杂度:O(n)O(n)。 我们需要 O(n)O(n) 的空间来存储计数结果。如果排除这一点,就只需要常数空间
class Solution {
public int[] countBits(int num) {
int[] ans = new int[num+1];
for (int i = 0; i <= num; i++) {
int res = calcOne(i);
ans[i] = res;
}
return ans;
}
//计算1的个数
private int calcOne(int i) {
int count = 0; //统计
while (i != 0){
i = i&(i-1); //清零最低位的1,清理一次,就统计一次
count++;
}
return count;
}
}
方法2:DP解法详情去看leetcode
190. 颠倒二进制位
颠倒给定的 32 位无符号整数的二进制位。
public class Solution {
// you need treat n as an unsigned value
public int reverseBits(int n) {
int ans = 0;
for (int i = 0; i < 32; i++) {
ans = (ans <<1) + (n&1); //(n&1) 得到最左边的数值,ans再左移一位,将最低位添加
n >>=1; //清除最左边的一位
}
return ans;
}
}