algorithms-Bit-Options

算法

常用的一个等式:-n = ~(n - 1) = ~n + 1

获得int型最大值

1
2
3
4
5
6
int getMaxInt(){
return (1 << 31) - 1;//2147483647,由于优先级关系,括号不可省略
return ~(1 << 31); //2147483647
return (1 << -1) - 1;//2147483647
return ((unsigned int) - 1) >> 1;//2147483647
}

获得int型最小值

1
2
3
4
int getMinInt(){
return 1 << 31;//-2147483648
return 1 << -1;//-2147483648
}

获得long类型的最大值

1
2
3
4
long getMaxLong(){
return ((unsigned long) - 1) >> 1;//2147483647 c语言版
return ((long)1 << 127) - 1;//9223372036854775807 java版
}

获得long最小值,和其他类型的最大值,最小值同理.

2运算

1
2
3
4
n << 1 // 乘以2
n >> 1 // 除以2
n << m // 乘以2的m次方
n >> m // 除以2的m次方

判断一个数的奇偶性

1
2
3
boolean isOddNumber(int n){
return (n & 1) == 1;
}

不用临时变量交换两个数(面试常考)

1
2
3
void swap(int *a,int *b){
(*a) ^= (*b) ^= (*a) ^= (*b);
}

通用版(一些语言中得分开写)

1
2
3
a ^= b;
b ^= a;
a ^= b;

取绝对值

(某些机器上,效率比n>0 ? n:-n 高)

1
2
3
4
5
6
int abs(int n){
return (n ^ (n >> 31)) - (n >> 31);
/* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1
若n为正数 n^0=0,数不变,若n为负数有n^-1 需要计算n和-1的补码,然后进行异或运算,
结果n变号并且为n的绝对值减1,再减去-1就是绝对值 */
}

取两个数的最大值

(某些机器上,效率比a>b ? a:b高)

1
2
3
4
int max(int a,int b){
return b & ((a-b) >> 31) | a & (~(a-b) >> 31);
/*如果a>=b,(a-b)>>31为0,否则为-1*/
}

C语言版

1
2
3
4
5
int max(int x,int y){
return x ^ ((x ^ y) & -(x < y));
/*如果x<y x<y返回1,否则返回0,
与0做与运算结果为0,与-1做与运算结果不变*/
}

取两个数的最小值

(某些机器上,效率比a>b ? b:a高)

1
2
3
4
int min(int a,int b){
return a & ((a-b) >> 31) | b & (~(a-b) >> 31);
/*如果a>=b,(a-b)>>31为0,否则为-1*/
}

C语言版

1
2
3
4
5
int min(int x,int y){
return y ^ ((x ^ y) & -(x < y));
/*如果x<y x<y返回1,否则返回0,
与0做与运算结果为0,与-1做与运算结果不变*/
}

判断符号是否相同

1
2
3
boolean isSameSign(int x, int y){ //有0的情况例外
return (x ^ y) >= 0; // true 表示 x和y有相同的符号, false表示x,y有相反的符号。
}

计算2的n次方

1
2
3
int getFactorialofTwo(int n){//n > 0
return 2 << (n-1);//2的n次方
}

判断一个数是不是2的幂

1
2
3
4
5
boolean isFactorialofTwo(int n){
return n > 0 ? (n & (n - 1)) == 0 : false;
/*如果是2的幂,n一定是100... n-1就是1111....
所以做与运算结果为0*/
}

对2的n次方取余

1
2
3
4
5
int quyu(int m,int n){//n为2的次方
return m & (n - 1);
/*如果是2的幂,n一定是100... n-1就是1111....
所以做与运算结果保留m在n范围的非0的位*/
}

求两个整数的平均值

1
2
3
4
5
6
7
8
int getAverage(int x, int y){
return (x + y) >> 1;
}
int getAverage(int x, int y){
return ((x ^ y) >> 1) + (x & y);
/*(x^y) >> 1得到x,y其中一个为1的位并除以2,
x&y得到x,y都为1的部分,加一起就是平均数了*/
}

下面是三个最基本对二进制位的操作

从低位到高位,取n的第m位

1
2
3
int getBit(int n, int m){
return (n >> (m-1)) & 1;
}

从低位到高位.将n的第m位置设为1

1
2
3
4
5
int setBitToOne(int n, int m){
return n | (1 << (m-1));
/*将1左移m-1位找到第m位,得到000...1...000
n在和这个数做或运算*/
}

从低位到高位,将n的第m位置设为0

1
2
3
4
5
int setBitToZero(int n, int m){
return n & ~(1 << (m-1));
/* 将1左移m-1位找到第m位,取反后变成111...0...1111
n再和这个数做与运算*/
}

另附一些对程序效率上没有实质提高的位运算技巧,一些也是位运算的常识(面试也许会遇到)

1
2
3
4
5
6
n+1 = -~n
n-1 = ~-n
-n = ~n+1
-n = (n^-1)+1
x = a ^ b ^ x <=> if(x == a) x = b; if(x == b) x = a;
sign(x) = !!n - (((unsigned)n >> 31) << 1)

获取整数二进制表示中最右侧的1

1
n & (-n)  <=> n & ~(n - 1)

二进制中1的个数

用到了n & (n - 1)
由x & (x - 1)消去x最后一位的1可知。不断使用 x & (x - 1) 消去x最后一位的1,计算总共消去了多少次即可。

1
2
3
4
5
6
7
8
int countOnes(int num) {
int count = 0;
while(num != 0) {
num = num & (num-1);
count++;
}
return count;
}

翻转

1
2
3
4
5
6
7
8
9
// 翻转
unsigned int Bit_Reverse(unsigned int v) {
v = ((v >> 1) & 0x55555555) | ((v << 1) & 0xaaaaaaaa);
v = ((v >> 2) & 0x33333333) | ((v << 2) & 0xcccccccc);
v = ((v >> 4) & 0x0f0f0f0f) | ((v << 4) & 0xf0f0f0f0);
v = ((v >> 8) & 0x00ff00ff) | ((v << 8) & 0xff00ff00);
v = ((v >> 16) & 0x0000ffff) | ((v << 16) & 0xffff0000);
return v;
}

输入两个数A和B,输出将A转换为B所需改变的二进制的位数。

首先,A异或B得到的是A和B中不相同位数组成的数,然后再求这个数二进制表示中1的个数,即为所求。

1
countOnes(A^B);

数组中只出现一次的数字

用到了n & (n - 1) 和 a ^ b ^ b = a

数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的数

因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或起来,就可以得到唯一的那个数。
参考文章:http://zhedahht.blog.163.com/blog/static/2541117420071128950682/

数组中,只有一个数出现一次,剩下都出现三次,找出出现一次的数

因为数是出现三次的,也就是说,对于每一个二进制位,如果只出现一次的数在该二进制位为1,那么这个二进制位在全部数字中出现次数无法被3整除。

膜3运算只有三种状态:00,01,10,因此我们可以使用两个位来表示当前位%3,对于每一位,我们让Two,One表示当前位的状态,B表示输入数字的对应位,Two+和One+表示输出状态。

参考文章:http://zhedahht.blog.163.com/blog/static/25411174201283084246412/

数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的数

有了第一题的基本的思路,我们可以将数组分成两个部分,每个部分里只有一个元素出现一次,其余元素都出现两次。那么使用这种方法就可以找出这两个元素了。
不妨假设出现一个的两个元素是x,y,那么最终所有的元素异或的结果就是res = x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。x,y在不同的两个子数组中。而且对于其他成对出现的元素,要么在x所在的那个数组,要么在y所在的那个数组。

位操作实现加减乘除运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//加法
int BinaryAdd(int a, int b) {
int carry, add;
do {
add = a ^ b; //该操作得到本位的加法结果
carry = (a & b) << 1; //该操作得到该位对高位的进位值
a = add;
b = carry;
} while (carry != 0); //循环直到某次运算没有进位,运算结束
return add;
}
//减法
int BinarySub(int a, int b) {
return BinaryAdd(a, BinaryAdd(~b, 1));
}
/*乘法
该过程中的bit_map是为了快速得到乘法过程中某位相乘的中间结果S[i]
需要位移的位数。bit_map的键值是2^0, 2^1,2^2, ……之类的数,对应的
值是0,1,2,……(即需要位移的位数)。
*/
int BinaryMultiply(int a, int b) {
bool neg = (b < 0);
if(b < 0)
b = -b;
int sum = 0;
map<int, int> bit_map;
for(int i = 0; i < 32; i++) {
bit_map.insert(pair<int, int>(1 << i, i));
}

while(b > 0) {
/*
b & ~(b - 1)可以得到乘数b的二进制表示中最右侧1的位置
last_bit记录被乘数a需要位移的位数
*/
int last_bit = bit_map[b & ~(b - 1)];
//将得到的乘法结果全部相加即为最后结果
sum += (a << last_bit);
b &= b - 1; //每次将b的二进制表示的最右侧1去掉用于下一次乘法
}
if(neg)
sum = -sum;
return sum;
}
//除法
int BinaryDivide(int a, int b){
bool neg = (a > 0) ^ (b > 0);
if(a < 0)
a = -a;
if(b < 0)
b = -b;
if(a < b)
return 0;
int msb = 0;
//msd记录除数需要左移的位数
for(msb = 0; msb < 32; msb++) {
if((b << msb) >= a)
break;
}
int q = 0; //记录每次除法的商
for(int i = msb; i >= 0; i--) {
if((b << i) > a)
continue;
q |= (1 << i);
a -= (b << i);
}
if(neg)
return -q;
return q;
}

reference:

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器