一篇文章搞懂位运算

位运算

  • 预备知识
    • Java、C++ 中的进制
    • 补码
  • 基本原理
    • 位与运算技巧
    • 移位运算
    • mask 计算
  • 常见二进制位变换操作
  • LeetCode
    • 位运算 —— 实战!!!

预备知识

Java、C++ 中的进制

我们最常见的进制就是十进制,当然,在计算机中最常见的就是二进制,除此之外,还有八进制十六进制等。

为了区分不同的进制,不同进制的数会带上 不同的前缀,如下表:

进制 前缀 举例
二进制 0b 0b1001
八进制 0 01001
十进制 没有前缀默认为十进制 1001
十六进制 0x 0x1001

补码

有符号整数通常用 补码表示和存储补码 具有如下特征:

  • 正整数补码与原码相同负整数补码为其原码 除符号位外的所有位取反后加 1
  • 可以将 减法运算 转化为 补码的加法运算 实现。
  • 符号位数值位 可以 一起参与运算

基本原理

0s 表示一串 01s 表示一串 1

x ^ 0s = x      x & 0s = 0      x | 0s = x
x ^ 1s = ~x     x & 1s = x      x | 1s = 1s
x ^ x = 0       x & x = x       x | x = x

⭐️ 异或

  • 利用 x ^ 1s = ~x 的特点,可以将一个数的位级表示 翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。
1^1^2 = 2

⭐️

  • 利用 x & 0s = 0x & 1s = x 的特点,可以实现掩码操作。一个数 nummask00111100 进行位与操作,只保留 num 中与 mask1 部分相对应的位。
01011011 &
00111100
--------
00011000

⭐️

  • 利用 x | 0s = xx | 1s = 1s 的特点,可以实现设值操作。一个数 nummask00111100 进行位或操作,将 num 中与 mask1 部分相对应的位都设置为 1
01011011 |
00111100
--------
01111111

位与运算技巧

⭐️ 得到最低的那一位 1

  • n & (-n) 得到 n 的位级表示中最低的那一位 1-n 得到 n 的反码加 1,也就是 -n = ~n + 1。例如对于二进制表示 10110100-n 得到 01001100,相与得到 00000100
10110100 &
01001100
--------
00000100

⭐️ 去除最低的那一位 1

  • n & (n-1) 去除 n 的位级表示中最低的那一位 1。例如对于二进制表示 01011011,减去 1 得到 01011010,这两个数相与得到 01011010
  • n-(n & (-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n & (n-1) 效果一样。
01011011 &
01011010
--------
01011010

移位运算

⭐️ 算术右移

  • >> n算术右移,相当于除以 2n,例如 -7 >> 2 = -2
11111111111111111111111111111001  >> 2
--------
11111111111111111111111111111110

⭐️ 无符号右移

  • >>> n无符号右移,左边会补上 0。例如 -7 >>> 2 = 1073741822
11111111111111111111111111111001  >>> 2
--------
00111111111111111111111111111111

⭐️ 算术左移

  • << n算术左移,相当于乘以 2n-7 << 2 = -28
11111111111111111111111111111001  << 2
--------
11111111111111111111111111100100

mask 计算

  • 要获取 111111111,将 0 取反即可,~0

  • 要得到只有第 i 位为 1mask,将 1 向左移动 i - 1 位即可,1 << (i-1) 。例如 1 << 4 得到只有第 5 位为 1mask00010000

  • 要得到 1i 位为 1mask(1< 即可,例如将 (1<<4)-1 = 00010000-1 = 00001111

  • 要得到 1i 位为 0mask,只需将 1i 位为 1mask 取反,即 ~((1<<i)-1)

⭐️ 将最高位及之后的每位都变成1

 mask |= mask >> 1;
 mask |= mask >> 2;
 mask |= mask >> 4;
 mask |= mask >> 8;
 mask |= mask >> 16;

如:对于 10000000 这样的数要扩展成 11111111,可以利用以下方法:

mask |= mask >> 1    11000000
mask |= mask >> 2    11110000
mask |= mask >> 4    11111111

常见二进制位变换操作

目的 行为 操作
去掉最后一位 101101->10110 x >> 1
在最后加一个0 101101->1011010 x << 1
在最后加一个1 101101->1011011 (x << 1) + 1
把最后一位变成1 101100->101101 x | 1
把最后一位变成0 101101->101100 (x | 1) - 1
最后一位取反 101101->101100 x ^ 1
把右数第K位变成1 101001->101101,k=3 x | (1 << (k - 1))
把右数第K位变成0 101101->101101,k=3 x & ~(1 << (k - 1))
右数第k位取反 101001->101101,k=3 x ^ (1 << (k - 1))
取末三位 1101101->101 x & 7
取末k位 1101101->1101,k=5 x & (1 << k - 1)
取右数第k位 1101101->1,k=4 x >> (k - 1) & 1
把末k位变成1 101001->101111,k=4 x | (1 << k - 1)
末k位取反 101001->100110,k=4 x ^ (1 << k - 1)
把右边连续的1变成0 100101111->100100000 x & (x + 1)
把右起第一个0变成1 100101111->100111111 x | (x + 1)
把右边连续的0变成1 11011000->11011111 x | (x - 1)
取右边连续的1 100101111->1111 (x ^ (x + 1)) >> 1
去掉右起第一个1的左边 100101000->1000 x & (x ^ (x - 1))

Java中的位操作

static int Integer.bitCount();           // 统计 1 的数量
static int Integer.highestOneBit();      // 获得最高位
static String toBinaryString(int i);     // 转换为二进制表示的字符串

C++中的位运算

int n = 22        	  //二进制为10110

cout << __builtin_parity(n) << endl;		//偶数个1,输出0;奇数个1,输出1
cout << __builtin_popcount(n) << endl;		//判断n的二进制中有多少个1
cout << __builtin_ctz(m) << endl;			//n前导0的个数, n = 0 时结果未定义
cout << __builtin_ffs(n) << endl;			//判断n的二进制末尾最后一个1的位置

LeetCode

位运算 —— 实战!!!

461. 汉明距离
136. 只出现一次的数字
268. 丢失的数字
260. 只出现一次的数字 III
190. 颠倒二进制位
231. 2 的幂 / 342. 4的幂
693. 交替位二进制数 / 476. 数字的补数
371. 两整数之和
318. 最大单词长度乘积
338. 比特位计数

注:仅供学习参考,如有不足,欢迎指正!!!

你可能感兴趣的:(LeetCode,leetcode,算法)