位运算经典应用

位运算经典应用


  1. 判断奇偶数

    //一般写法,虽然编译器会自动优化成位运算
    if(( n%2 ) == 1){
    	//n是个奇数
    }
    
    //如果把 n 以二进制的形式展示的话,其实我们只需要判断最后一个二进制位是 1 还是 0 就行了,如果是 1 的话,代表是奇数,如果是 0 则代表是偶数
    if(n & 1 == 1){
    	//n是个奇数
    }
    
  2. 交换两个数

    //传统代码
    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);
    
  3. 找出没有重复的数

    给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数 。

    //直接把它们全部异或一下即可,出现了两次的数异或之后会变成0,那个出现一次的数,和 0 异或之后就等于它本身
    int find(int[] arr){
        int tmp = arr[0];
        for(int i = 1;i < arr.length; i++){
            tmp = tmp ^ arr[i];
        }
        return tmp;
    }
    
  4. 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)
    
  5. 找出不大于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;
    }
    
  6. 获取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
    }
    
  7. 获取int型最小值

    //写法1
    int getMinInt(){
    	return 1 << 31;//-2147483648
     }
     //写法2
     int getMinInt(){   //有些编译器不适用
    	return 1 << -1;//-2147483648
    }
    
  8. 取绝对值

    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就是绝对值 */
    }
    
  9. 取两个数的最大值

    //通用版
    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做与运算结果不变*/
    }
    
  10. 判断一个数是不是2的幂

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

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

    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的部分,加一起就是平均数了*/
     
    } 
    
  13. 三个最基本对二进制位的操作

    //从低位到高位,取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再和这个数做与运算*/
    }
    
  14. 统计n在二进制中1的个数

    二进制数二进制值用处
    0xAAAAAAAA10101010101010101010101010101010偶数位为1,以1位为单位提取奇位
    0x5555555501010101010101010101010101010101奇数位为1,以1位为单位提取偶位
    0xCCCCCCCC11001100110011001100110011001100以“2位”为单位提取奇位
    0x3333333300110011001100110011001100110011以“2位”为单位提取偶位
    0xF0F0F0F011110000111100001111000011110000以“4位”为单位提取奇位
    0x0F0F0F0F00001111000011110000111100001111以“4位”为单位提取偶位
    0xFFFF000011111111111111110000000000000000以“16位”为单位提取奇位
    0x0000FFFF00000000000000001111111111111111以“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,计算总共消去了多少次即可
    
  15. 对于正整数的取模运算(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;
    }
    
  16. 计算掩码

    //获得数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))
    
  17. 消去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的个数
    */
    
  18. 使用二进制进行子集枚举

    应用.给定一个含不同整数的集合,返回其所有的子集
    样例
    如果 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}
    
  19. 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);
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值