位操作与使用位操作解题的基本思路

如何通过位运算巧解编程题

概念

位运算是一种针对于小于一个字节数据进行的数学运算。计算机编程中,需要进行位运算的操作包括底层硬件控制,错误检测与修正算法,数据压缩,加密算法,优化等。对于大多数其他任务来讲,现代编程语言使得程序员可以直接对高层次的抽象进行操作而不用直接进行位操作。在源码中,主要用到的位操作包括了:AND,OR,XOR,NOT,和按位移。

由于位运算中对位的处理是并行的,所以位操作在某些情况下可以省去某些操作中对于数据结构的循环便利从而提高运算效率。但同时,位操作使得代码变得难以理解与维护。

基础

位运算中包括了一些不同的作用于位级别的操作,这些操作非常快,可以用于优化时间复杂度。一些基本的位操作包括了以下一些:

NOT(~) : 按位取反返回一个数字的补数,例如如果某一位是0,它会将其变成1,反之亦然。

N = 5 = (101)2

~N = ~5 = ~(101)2 = (010)2 = 2

AND(&) : 按位与是一个作用于相同长度位模式的二元操作符。只在参与比较的两个位同为1时,返回1,否则返回0。

A = 5 = (101)2 , B = 3 = (011)2

A & B = (101)2 & (011)2= (001)2 = 1

OR(|) : 按位或同样也是作用于两个相同位长度数字的二元操作符,当参与比较的两个位其中有一个为1时,返回1,当两个位都是0时,返回0。

A = 5 = (101)2 , B = 3 = (011)2

A | B = (101)2 | (011)2 = (111)2 = 7

XOR(^) : 异或同为二元操作符,当两个参与比较的位不同时返回1,否则返回0。

A = 5 = (101)2 , B = 3 = (011)2

A ^ B = (101)2 ^ (011)2 = (110)2 = 6

Left Shift (<<) : 左移操作符是一个二元操作符,其将操作数字按位左移一定位数并在末尾补0。左移操作相当于操作数与2k相乘(k为左移位数)。

1 << 1 = 2 = 21

1 << 2 = 4 = 22

1 << 3 = 8 = 23

1 << n = 2n

Right Shift (>>) : 右移操作符同为二元操作符,其将作用数字按位向右移动,并在末端补首位补0。右移操作相当于操作数与2k相除(k为右移位数)。

4 >> 1 = 2

6 >> 1 = 3

5 >> 1 = 2

16 >> 4 = 1

X Y X&Y X/ Y X^Y
0 0 0 0 0 1
0 1 0 1 1 1
1 0 0 1 1 0
1 1 1 1 0 0

应用示例

  1. 计算一个数字的二进制表示中有多少个一

    //每一次n&(n-1)操作  都从末尾向前去掉一个1  直到数字减少为0
      int count_one(int n) {
        while(n) {
            n = n&(n-1);
            count++;
        }
        return count;
      }
    
  2. 判断是否是为4次冪

    //!(n&(n-1))确定二进制中只有一个1,n&0x55555555确定1的位置
    bool isPowerOfFour(int n) {
        return !(n&(n-1)) && (n&0x55555555);
        //check the 1-bit location;
    }
    
  3. 使用^与&运算将两个数字相加

    // 通过a^b得到没有进位的结果,通过a&b得到进位,递归相加
    int getSum(int a, int b) {
        return b==0? a:getSum(a^b, (a&b)<<1); //be careful about the       terminating condition;
    }
    
  4. 从一个连续数组0,1,2,3,...,n中,找出缺少的一个数字,例如num=[0,1,3],则返回2。

    //由于相同的数字会因为^抵消,所以ret最后返回的是缺少数字与数组中最大数的^,这时候在与数组中最大数做^,很自然的得到缺少的数字。
    int missingNumber(vector<int>& nums) {
        int ret = 0;
        for(int i = 0; i < nums.size(); ++i) {
            ret ^= i;
            ret ^= nums[i];
        }
        return ret^=nums.size();
    }
    
  5. 找到最接近给定数字N的2的n词幂

    //实际就是定位数字最大端1的位置,将所有位置1后加一右移,即得到答案
    long largest_power(long N) {
        //changing all right side bits to 1.
        N = N | (N>>1);
        N = N | (N>>2);
        N = N | (N>>4);
        N = N | (N>>8);
        N = N | (N>>16);
        return (N+1)>>1;
    }
    

    N | (N>>1)后

    N | (N>>4) 后

    N | (N>>4) 后

    N | (N>>8)后

  6. 将一个无符号32位数字的所有位取反

    //从左到右遍历N并从右到左置res中的1,或者相反方向。
    uint32_t reverseBits(uint32_t n) {
        unsigned int mask = 1<<31, res = 0;
        for(int i = 0; i < 32; ++i) {
            if(n & 1) res |= mask;
            mask >>= 1;
            n >>= 1;
        }
        return res;
    }
    
    uint32_t reverseBits(uint32_t n) {
       uint32_t mask = 1, ret = 0;
       for(int i = 0; i < 32; ++i){
          ret <<= 1;
          if(mask & n) ret |= 1;
             mask <<= 1;
           }
       return ret;
    }
    
    x = ((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1);
    x = ((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2);
    x = ((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4);
    x = ((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8);
    x = ((x & 0xffff0000) >> 16) | ((x & 0x0000ffff) << 16);
    
  7. 给定范围[m,n],且0<=m<=n<=2147483647,返回范围内所有整数按位与的结果,如给定范围[5,7],则返回4。

    int rangeBitwiseAnd(int m, int n) {
       int a = 0;
       while(m != n) {
          m >>= 1;
          n >>= 1;
          a++;
       }
       return m<<a; 
    }
    
  8. 验证一个给定数字是否是2的n次方

    //2的n次方的二进制表达中只会有1个1,而-1操作会将从左向右遇到的第一个变成0,而这一位之前的数字变成1
    //所以如果X只有一个1,那么-1后,除了1的那一位,其他都会变为1,这样两个数字求与,应该得到0.
    bool isPowerOfTwo(int x)
    {
       // x will check if x == 0 and !(x & (x - 1)) will check if x is a power of 2 or not
       return (x && !(x & (x - 1)));
    }
    
  9. 写一个程序返回无符号数里有多少个1

    //参照上一题,只要N&(N-1)仍然大于0,即可知,N中仍然有1存在。
    int hammingWeight(uint32_t n) {
       int count = 0;
       while(n) {
          n = n&(n-1);
          count++;
       }
       return count;
    }
    
    int hammingWeight(uint32_t n) {
       ulong mask = 1;
       int count = 0;
       for(int i = 0; i < 32; ++i){ //31 will not do, delicate;
          if(mask & n) count++;
          mask <<= 1;
       }
       return count;
    }
    
  10. 如果得到一个集合的所有子集
    一个大小为N的集合,有2n个可能的子集,如果把每一个元素当做一个位,2N的二进制表达正好有N位,把某一位值为1当做存在,值为0时当做不存在,以此正好可以通过二进制表达来确定每一个子集的元素。

possibleSubsets(A, N):
   for i = 0 to 2^N:
      for j = 0 to N:
         if jth bit is set in i:
            print A[j]
                print ‘\n’
  1. 返回X二进制表示中最靠右的一个1
//x-1 将最靠右的一个1置0,并将从这个1一直到最右的位置1,与x进行与操作后,实际上是将最右的一个1置0,与原数字异或操作后,得到这个1。
int rightMostOne(int x){
   return x^x&(x-1);
}

// -x = ~x + 1 所以 -x 除了最右的一个1,和这个1往右的所有数字与原数相等,
int rightMostOne(int x){
   return x&(-x);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容