常用的位运算
位运算符 | 说明 | 计算 | 案例 |
---|---|---|---|
& | 与 | 都是1为1, 否则即为 0 | 4 & 3 = 0100 & 0011 = 0000 == 0 |
| | 或 | 有一个为1就是1 | 4 & 3 = 0100 | 0011 = 0111 == 7 |
^ | 异或 | 不同为1 相同为0 | 7 & 3 = 0111 ^ 0011 = 0100 == 4 |
~ | 取反 | 二进制的0变成1,1变成0 | ~4 = 11111111111111111111111111111011 = -5 |
<< | 左移 | 左移后右边位补 0 | 1<<3 = 8 |
>> | 带符号右移 | 右移后左边位补符号位 | -8>>2 = -2 |
>>> | 不带符号右移 | 右移后左边位补 0 | -8>>>2 = 2 |
原码、反码、补码是什么
正数的原码、反码、补码都是二进制本身;
负数反码就是原码除了符号位不动,其他所有位按位取反
负数的补码是反码加一得到的(运算时包括符号位)
数值 | 原码 | 反码 | 补码 |
---|---|---|---|
+8 | 0000 1000 | 0000 1000 | 0000 1000 |
-8 | 1000 1000 | 1111 0111 | 1111 1000 |
为什么负数的二进制设计为正数取反加1
在计算机中,数值都是用补码来计算和存储的。使用补码的原因是:
- 使用补码可以将符号位和数值域统一处理
- 在硬件电路的实现上,只要使用一种加法电路就可以处理各种有符号数的加减计算,使得电路设计简单
为什么1字节的整数范围是-128到127,而不是-128到128
因为最高位代表符号位:1表示负数, 对半分就是 2^7 = 128 负数范围 -1 到 -128;正数范围 0 到 127。
位运算小技巧
取相反数
num = -num;
num = ~num + 1
取模运算
num & 63 等同于 num % 64
因为取模64就等同于舍弃高位数据 只保留低6位的数据
63的二进制位就是 0000 0000 0011 1111,和num做与运算就是舍弃掉高位信息
交换两个数
原理: a ^ 0 = a ; a ^ a = 0
private static void swap (int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j]; // arr[i] ^ arr[j] ^ arr[j] = arr[i]
arr[i] = arr[i] ^ arr[j]; // arr[i] ^ arr[j] ^ arr[i] = arr[j]
}
判断奇偶数
原理:num & 1 如果最后一位是1则代表是奇数 结果不等于0。否则为偶数
System.out.println( (3 & 1) == 0);
打印整数的二进制信息
原理:int类型占用4字节一共32bit, 打印31位到0位的bit位,1左移之后 其他的bit位都是0,如果31位与num等于0 则31位肯定是0
private static void print(int num) {
int c = 0;
for (int i = 31; i >= 0; i--) {
c++;
System.out.print( (num & (1 << i)) == 0 ? "0" : "1");
//按4分隔加空格
if((c & 3) == 0) {
System.out.print(" ");
}
}
}
只出现奇数次的一个数字
问题描述
给定一个非空整数数组,除了某个元素出现奇数次以外,其余每个元素均出现偶数次。找出那个只出现了奇数次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
public int onceNum(int[] arr) {
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
return eor;
}
只出现奇数次的两个数字
问题描述
给定一个非空整数数组,除了两个不同元素出现奇数次以外,其余每个元素均出现偶数次。找出出现奇数次的两个元素。
思路:
- 先将数组中的每个数进行异或,最后的结果肯定是a^b
- a不等于b 则 a ^ b的结果必定有二进制位等于1的数字(这个1肯定是a 和 b的二进制位不同异或成1的)
- 将数组分为两部分,a 、b的数字必然在二进制位为1、0的位置
- 将二进制位等于1的部分进行异或 剩下的一个数必然是a或者b (假设是a)
- 将找到的数 a ^ a ^ b = b 得到另外的 b
public int[] twoNum(int[] arr){
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
// eor = a ^ b 不为0 必然有二进制位为1
// 提取出最右的1
int rightOne = eor & (~eor + 1);
int onlyOne = 0;
for (int i = 0 ; i < arr.length;i++) {
// arr[1] = 111100011110000
// rightOne= 000000000010000
if ((arr[i] & rightOne) != 0) {
onlyOne ^= arr[i];
}
}
// System.out.println(onlyOne + " " + (eor ^ onlyOne));
return new int[]{onlyOne, eor ^ onlyOne};
}
位图
public static class BitMap {
private long[] bits;
/**
* bit长度62 则需要一个long字段存储 (62 + 64) / 64
* @param max 位图大小
*/
public BitMap(int max){
this.bits = new long[ (max + 64) >> 6];
}
/**
* num & 63 等同于 num % 64
* 因为取模64就等同于舍弃高位数据 只保留低6位的数据
* 63的二进制位就是 0000 0000 0011 1111
*
* 添加150 则属于数组中 150/64索引处,bits[2] = bits[2] | 1L << (150 % 64 = 22)
* bits[2] 和 1左移后的数据做或运算
* 则将bits[2]中的long数据22位的bit位设置为1
* @param num 添加数据到位图中
*/
public void add(int num){
//注意 必须写1L 不能写1, 否则1当做int无法左移超过31位
bits[num >> 6] |= (1L << (num & 63));
}
/**
* 删除某个数 只要保证bits[num/64] & 1111 1111 0111 1111 就能删除掉对应位数据
* 先做出来 0000 0000 1000 0000 然后取反就可以得到上面的数
* @param num 删除位图中数据
*/
public void delete(int num) {
bits[num >> 6] &= ~(1L << (num & 63));
}
/**
* 与运算 如果不等于0 说明数据存在
* @param num
* @return 数据是否存在位图中
*/
public boolean contains(int num) {
return (bits[num >> 6] & (1L << (num & 63))) != 0;
}
}
统计某个数二进制位=1的个数
public static int bitCount(int num){
int count = 0;
while(num != 0) {
//num取反 + 1 与 num就能得到二进制最右边为1的数字
int right = num & ((~num) + 1);
count++;
num ^= right;
}
return count;
}
位运算实现加减乘除
异或运算就等于无进位相加
加法
public static int add(int a, int b) {
int sum = a;
while (b != 0) {
//计算a 异或 b 无进位相加
sum = a ^ b;
// a & b 向左移动一位得到进位信息
b = (a & b) << 1;
// b不等0说明还需要重新相加
a = sum;
}
return sum;
}
减法
/**
* @return 返回相反数
*/
public static int negNum(int n) {
return add(~n, 1);
}
/**
* a - b 等同于 a + (~b + 1)
*/
public static int minus(int a, int b) {
return add(a, negNum(b));
}
乘法
public static int multi(int a, int b) {
int res = 0;
while (b != 0) {
if ((b & 1) != 0) {
res = add(res, a);
}
a <<= 1;
b >>>= 1;
}
return res;
}
除法
public static boolean isNeg(int n) {
return n < 0;
}
private static int div(int a, int b) {
int x = isNeg(a) ? negNum(a) : a;
int y = isNeg(b) ? negNum(b) : b;
int res = 0;
for (int i = 30; i >= 0; i = minus(i, 1)) {
if ((x >> i) >= y) {
res |= (1 << i);
x = minus(x, y << i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
public static int divide(int a, int b) {
if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
return 1;
} else if (b == Integer.MIN_VALUE) {
return 0;
} else if (a == Integer.MIN_VALUE) {
if (b == negNum(1)) {
return Integer.MAX_VALUE;
} else {
int c = div(add(a, 1), b);
return add(c, div(minus(a, multi(c, b)), b));
}
} else {
return div(a, b);
}
}