位运算经典应用
-
判断奇偶数
//一般写法,虽然编译器会自动优化成位运算 if(( n%2 ) == 1){ //n是个奇数 } //如果把 n 以二进制的形式展示的话,其实我们只需要判断最后一个二进制位是 1 还是 0 就行了,如果是 1 的话,代表是奇数,如果是 0 则代表是偶数 if(n & 1 == 1){ //n是个奇数 }
-
交换两个数
//传统代码 int temp = x; x = y; y = temp; //位运算,不需要额外的辅助变量 x = x ^ y; //(1) y = x ^ y; //(2) x = x ^ y; //(3) //推导:易知 一个数异或本身结果为0,任何数异或0等于其本身,且异或运算支持交换律和结合律 //将(1)带入(2) y = (x^y)^y = x^(y^y) = x^0 = x;即x的值赋给了y //式(3) x = (x^y)^x = (x^x)^y = 0^y = y(这里是未赋值前的y);
-
找出没有重复的数
给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数 。
//直接把它们全部异或一下即可,出现了两次的数异或之后会变成0,那个出现一次的数,和 0 异或之后就等于它本身 int find(int[] arr){ int tmp = arr[0]; for(int i = 1;i < arr.length; i++){ tmp = tmp ^ arr[i]; } return tmp; }
-
m的n次方
不能使用系统自带的 pow 函数
int pow(int m, int n){ int sum = 1; while(n != 0){ if(n & 1 == 1){ sum *= m; } m *= m; n = n >> 1; } return sum; } //复杂度O(logn)
-
找出不大于N的最大的2的幂指数
//传统解法,复杂度O(logn) int findN(int N){ int sum = 1; while(true){ if(sum * 2 > N){ return sum; } sum = sum * 2; } } //位运算,例如 N = 19,那么转换成二进制就是 00010011。 //那么我们要找的数就是,把二进制中最左边的 1 保留,后面的 1 全部变为 0。 //即我们的目标数是 00010000。那么如何获得这个数呢?解法如下: //1.找到最左边的 1,然后把它右边的所有 0 变成 1 //2.把得到的数值加 1,可以得到 00100000即 00011111 + 1 = 00100000 //3.把 得到的 00100000 向右移动一位,即可得到 00010000,即 00100000 >> 1 = 00010000 //代码 时间复杂度近似O(1)!!! int findN(int n){ n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8 // 整型一般是 32 位,上面我是假设 8 位。 return (n + 1) >> 1; }
-
获取int型最大值
//写法1 int getMaxInt(){ return (1 << 31) - 1;//2147483647, 由于优先级关系,括号不可省略 } //写法2 int getMaxInt(){ return ~(1 << 31);//2147483647 } //写法3 int getMaxInt(){//有些编译器不适用 return (1 << -1) - 1;//2147483647 } //不知道int占几个字节时 int getMaxInt(){ return ((unsigned int) - 1) >> 1;//2147483647 }
-
获取int型最小值
//写法1 int getMinInt(){ return 1 << 31;//-2147483648 } //写法2 int getMinInt(){ //有些编译器不适用 return 1 << -1;//-2147483648 }
-
取绝对值
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就是绝对值 */ }
-
取两个数的最大值
//通用版 int max(int a,int b){ return b & ((a-b) >> 31) | a & (~(a-b) >> 31); /*如果a>=b,(a-b)>>31为0,否则为-1*/ } //C语言版 int max(int x,int y){ return x ^ ((x ^ y) & -(x < y)); /*如果x<y x<y返回1,否则返回0, 、 与0做与运算结果为0,与-1做与运算结果不变*/ }
-
判断一个数是不是2的幂
boolean isFactorialofTwo(int n){ return n > 0 ? (n & (n - 1)) == 0 : false; /*如果是2的幂,n一定是100... n-1就是1111.... 所以做与运算结果为0*/ }
-
对2的n次方取余
int quyu(int m,int n){//n为2的次方 return m & (n - 1); /*如果是2的幂,n一定是100... n-1就是1111.... 所以做与运算结果保留m在n范围的非0的位*/ }
-
求两个数的平均值
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位 int getBit(int n, int m){ return (n >> (m-1)) & 1; } //从低位到高位,将n的第m位置1 int setBitToOne(int n, int m){ return n | (1 << (m-1)); /*将1左移m-1位找到第m位,得到000...1...000 n在和这个数做或运算*/ } //从低位到高位,将n的第m位置0 int setBitToZero(int n, int m){ return n & ~(1 << (m-1)); /* 将1左移m-1位找到第m位,取反后变成111...0...1111 n再和这个数做与运算*/ }
-
统计n在二进制中1的个数
二进制数 二进制值 用处 0xAAAAAAAA 10101010101010101010101010101010 偶数位为1,以1位为单位提取奇位 0x55555555 01010101010101010101010101010101 奇数位为1,以1位为单位提取偶位 0xCCCCCCCC 11001100110011001100110011001100 以“2位”为单位提取奇位 0x33333333 00110011001100110011001100110011 以“2位”为单位提取偶位 0xF0F0F0F0 11110000111100001111000011110000 以“4位”为单位提取奇位 0x0F0F0F0F 00001111000011110000111100001111 以“4位”为单位提取偶位 0xFFFF0000 11111111111111110000000000000000 以“16位”为单位提取奇位 0x0000FFFF 00000000000000001111111111111111 以“16位”为单位提取偶位 //32位无符号数的1的个数 int CountOne(unsigned int n) { //0xAAAAAAAA,0x55555555分别是以“1位”为单位提取奇偶位 n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555); //0xCCCCCCCC,0x33333333分别是以“2位”为单位提取奇偶位 n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333); //0xF0F0F0F0,0x0F0F0F0F分别是以“4位”为单位提取奇偶位 n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F); //0xFF00FF00,0x00FF00FF分别是以“8位”为单位提取奇偶位 n = ((n & 0xFF00FF00) >> 8) + (n & 0x00FF00FF); //0xFFFF0000,0x0000FFFF分别是以“16位”为单位提取奇偶位 n = ((n & 0xFFFF0000) >> 16) + (n & 0x0000FFFF); return n; } //法2 用 n & (n-1) 循环消去最后一个1,计算总共消去了多少次即可
-
对于正整数的取模运算(Tips:负数不能这么算)
n & ((1<<k)-1) //n对2的倍数取模 // 快速计算 (a ^ p) % m 的值 __int64 FastM(__int64 a, __int64 p, __int64 m) { if (p == 0) return 1; __int64 r = a % m; __int64 k = 1; while (p > 1) { if ((p & 1)!=0) { k = (k * r) % m; } r = (r * r) % m; p >>= 1; } return (r * k) % m; }
-
计算掩码
//获得数x的低n位的集合 template<class Type> inline Type LowByte(Type x, int n) { return x & ((1 << n) - 1); } //简化 #define LOWBYTE(x,n) x & ((x << n) - 1) //获得数x的高n位的集合 template<class Type> inline Type HeightByte(Type x, int n) { return x & (((1 << n) - 1) << (sizeof(x)-n)); } //简化 #define HEIGHTBYTE(x,n) x & (((1 << n) - 1) << (sizeof(x)-n))
-
消去x的最后一位的1
x & (x-1) x = 1100 x-1 = 1011 x & (x-1) = 1000; //应用 //将整数A转换为B,需要改变多少个bit位 /*思考将整数A转换为B,如果A和B在第i(0<=i<32)个位上相等,则不需要改变这个BIT位,如果在第i位上不相等,则需要改变这个BIT位。所以问题转化为了A和B有多少个BIT位不相同。联想到位运算有一个异或操作,相同为0,相异为1,所以问题转变成了计算A异或B之后这个数中1的个数 */
-
使用二进制进行子集枚举
应用.给定一个含不同整数的集合,返回其所有的子集
样例
如果 S = [1,2,3],有如下的解:
[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2] ]思路
思路就是使用一个正整数二进制表示的第i位是1还是0,代表集合的第i个数取或者不取。所以从0到2n-1总共2n个整数,正好对应集合的2^n个子集。S = {1,2,3} N bit Combination 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^b^b=a
-
应用一 数组中,只有一个数出现一次,剩下都出现二次,找出出现一次的。
问题:Given [1,2,2,1,3,4,3], return 4
解题思路:因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或起来,就可以得到唯一的那个数。
代码:
#include<stdio.h> int main() { int a[7]={1,2,2,1,3,4,3}; int ans=0; for(int i=0;i<7;i++){ ans^=a[i]; } printf("%d\n",ans); }
-
应用二 数组中,只有一个数出现一次,剩下都出现三次,找出出现一次的。
问题:Given [1,1,2,3,3,3,2,2,4,1] return 4解题思路:因为数是出现三次的,也就是说,对于每一个二进制位,如果只出现一次的数在该二进制位为1,那么这个二进制位在全部数字中出现次数无法被3整除。
模3运算只有三种状态:00,01,10,因此我们可以使用两个位来表示当前位%3,对于每一位,我们让Two,One表示当前位的状态,B表示输入数字的对应位,Two+和One+表示输出状态。0 0 0 0 0 0 0 1 0 1 0 1 0 0 1 0 1 1 1 0 1 0 0 1 0 1 0 1 0 0 One+ = (One ^ B) & (~Two) Two+ = (~One+) & (Two ^ B)
代码:
#include<stdio.h> void findNum(int *a,int n) { int one=0; int two=0; int i,j,k; for(i=0;i<n;i++){ two=two|(one&a[i]); one=one^a[i]; int three=two&one; two=two^three; one=one^three; } printf("%d\n",one|two); } int main() { int a[10]={1,1,2,3,3,3,2,2,4,1}; findNum(a,10); } //另一种容易理解的方法 #include<stdio.h> void findNum(int *a,int n) { int ans=0; int bits[32]={0}; for(int i=0;i<n;i++){ for(int j=0;j<32;j++){ bits[j]+=((a[i]>>j)&1); } } for(int i=0;i<32;i++){ if(bits[i]%3==1) ans+=1<<i; } printf("%d\n",ans); } int main() { int a[10]={1,1,2,3,3,3,2,2,4,1}; findNum(a,10); }
-
应用三 数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的
问题:Given [1,2,2,3,4,4,5,3] return 1 and 5
解题思路:有了第一题的基本的思路,我们不妨假设出现一个的两个元素是x,y,那么最终所有的元素异或的结果就是res = x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1,那么这一位1对于这两个数x,y只有一个数的该位置是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。求出x,y其中一个,我们就能求出两个了。
代码:#include<stdio.h> void findNum(int *a,int n) { int ans=0; int pos=0; int x=0,y=0; for(int i=0;i<n;i++) ans^=a[i]; int tmp=ans; while((tmp&1)==0){ //终止条件是二进制tmp最低位是1 pos++; tmp>>=1; } for(int i=0;i<n;i++){ if((a[i]>>pos)&1){ //取出第pos位的值 x^=a[i]; } } y=x^ans; if(x>y) swap(x,y); //从大到小输出x,y printf("%d %d\n",x,y); } int main() { int a[8]={1,2,2,3,4,4,5,3}; findNum(a,8); } //另一种写法 #include<stdio.h> void findNum(int *a,int n) { int diff=0; int x=0,y=0; for(int i=0;i<n;i++){ diff^=a[i]; } diff&=-diff; //取diff的最后一位1的位置 for(int i=0;i<n;i++){ if((a[i]&diff)==0){ x^=a[i]; }else y^=a[i]; } if(x>y) swap(x,y); printf("%d %d\n",x,y); } int main() { int a[10]={1,2,2,3,4,4,5,3}; findNum(a,8); }
//另一种写法
#include<stdio.h>
void findNum(int *a,int n)
{
int diff=0;
int x=0,y=0;
for(int i=0;i<n;i++){
diff^=a[i];
}
diff&=-diff; //取diff的最后一位1的位置
for(int i=0;i<n;i++){
if((a[i]&diff)==0){
x^=a[i];
}else y^=a[i];
}
if(x>y) swap(x,y);
printf("%d %d\n",x,y);
}
int main()
{
int a[10]={1,2,2,3,4,4,5,3};
findNum(a,8);
}