文章目录
位运算 (TODO)
导言
- 以下代码都存放于 我的GitHub仓库 ,如果小伙伴觉得有用,请给我颗星星哈。
- 以下代码都是提交过的,正确性可以保证。
1. 位运算性质
- 「异或」运算
- 满足交换律:
x ^ y == y ^ x
- 满足结合律:
(x ^ y) ^ z == x ^ (y ^ z)
- 自身异或为
0
:x ^ x == 0
- 任何数和
0
异或都为自身:x ^ 0 == x
- 满足交换律:
- 「与」运算
- 满足交换律:
x & y == y & x
- 满足结合律:
(x & y) & z == x & (y & z)
- 自身相与为自身:
x & x == x
- 任何数和
0
相与都为0
:x & 0 == 0
- 满足交换律:
- 「或」运算
- 满足交换律:
x | y == y | x
- 满足结合律:
(x | y) | z == x | (y | z)
- 自身相或为自身:
x | x == x
- 任何数和
0
相或都为自身:x | 0 == x
- 满足交换律:
2. 小技巧
-
获取
整数x
的最低位1
对应的值:x & (-x)
-
获取
整数x
的最高位1
对应的值:// 适用于64位的整数 (最高能获取第 62 位的"1" ) func getHighestOne(a int) int { for i := 1; i <= 32; i *= 2 { a |= a >> uint8(i) } return (a + 1) >> 1 }
-
去除
整数x
的最低位1
后的值:x & (x-1)
-
实现
整数x
与整数y
的不进位加法:x ^ y
-
实现
0
与1
的相互转换:0 ^ 1 == 1
1 ^ 1 == 0
-
关于取模
- 获取
整数x
模2
后的值:x & 1
- 获取
整数x
模4
后的值:x & 3
- 获取
整数x
模8
后的值:x & 7
- …
- 获取
3. 进阶
3.1 判断 整数x
是否为 2
的幂
func isPowerOfTwo(x int) bool {
return x > 0 && x&(x-1) == 0
}
3.2 判断 整数x
是否为 4
的幂
// 只适用于32位整数
func isPowerOfFour(x int) bool {
return isPowerOfTwo(x) && x & 0x55555555 != 0
}
// 适用于32、64位整数,但不适用于32位系统
func isPowerOfFour(x int) bool {
return isPowerOfTwo(x) && x & 0x5555555555555555 != 0
}
// 适用于32、64位整数,适用于32、64位系统
func isPowerOfFour(x int) bool {
return isPowerOfTwo(x) && x%3 != 0
}
func isPowerOfTwo(x int) bool {
return x > 0 && x&(x-1) == 0
}
3.3 利用位运算实现 整数x
、整数y
的交换。
// 这里只是形参的交换,如果要实现实参的交换,那么可以传入指针。
func swap(x,y int) {
x = x ^ y
y = x ^ y
x = x ^ y
}
3.4 利用位运算获取 整数x
有多少个 1
。
func hammingWeight(x int) int {
count := 0
for x != 0 {
count++
x = x & (x-1)
}
return count
}
3.5 计算 整数x
、整数y
的有多少个不同的二进制位。
func hammingDistance(x,y int) int {
return hammingWeight(x^y)
}
func hammingWeight(x int) int {
count := 0
for x != 0 {
count++
x = x & (x-1)
}
return count
}
3.6 利用位运算实现不用 +
做加法。
func getSum(a int, b int) int {
sum, carry := a^b, (a&b)<<1
if carry == 0 {
return sum
}
return getSum(sum, carry)
}
3.7 位运算还可以实现的bitmap
。
/*
现在假设现在有 personCount 人(标号[0, personCount-1]),他们每个人最多拥有30类物品(标号[0,29]),每类物品数量范围为[0,10^6]。
现在给出他们分发物品的情况,每一种情况用一个长度为 3 的切片表示。
如 [0, 5, 10],表示第 0 个人,被分发了 10 个第 5 类物品。
如 [5, 10, 1000],表示第 5 个人,被分发了 1000 个第 10 类物品。
现在给出人物编号 x、y,我想知道第 x 个人和第 y 个人 他们有多少类相同的物品、多少类不同的物品、以及他们一共有多少类物品。
以上输入都在合法范围内。
*/
// 解题代码 (未经验证,只是为了说明位运算的功能,如果有错误,欢迎大家指出哈)
// personCount: 一共有多少人
// situations: 包含很多种拥有情况
// x,y : 两个人的标号
func solve(personCount int, situations [][]int, x, y int) (sameKinds, unsameKinds, allKinds int) {
personOwn := make([]int, personCount) // 用于表示每个人的物品拥有情况(不关心拥有多少个,0个表示没有,否则表示有)
// 这里使用 32 位整数表示人们拥有物品的情况:
// 假如 personOwn[i] = 7, 这表示第 i 个人拥有第0、第1、第2类物品。
// 解释: 对于32位整数来说,7的二进制是: 00000000000000000000000000000111,从右到左算,标1的位置表示拥有该类物品,否则为不拥有。
// 假如 personOwn[i] = 9, 这表示第i个人拥有第0、第3类物品。
// 解释: 对于32位整数来说,9的二进制是: 00000000000000000000000000001001,从右到左算,标1的位置表示拥有该类物品,否则为不拥有。
// 物品分配
for i := 0; i < len(situations); i++ {
if situations[i][2] != 0 {
personOwn[situations[i][0]] &= 1 << uint8(situations[i][1])
}
}
// 相同的物品表示 2 个人该二进制位上都是1,所以使用 & 运算,再通过计算结果有多少个 1,就能知道有多少类相同的物品。
sameKinds = countOne(personOwn[x] & personOwn[y])
// 不同的物品表示 2 个人该二进制位上是不同的,所以使用 ^ 运算,再通过计算结果有多少个 1,就能知道有多少类不同的物品。
unsameKinds = countOne(personOwn[x] ^ personOwn[y])
// 对于一共有多少类物品,两个人在该二进制位上有一个 1 就可以了,再通过计算结果有多少个 1,就能知道他们一共有多少类物品。
allKinds = countOne(personOwn[x] | personOwn[y])
// 例子:
// 如 personOwn[x] == 00000000000000000000000000000111
// personOwn[y] == 00000000000000000000000000001001
// 则 personOwn[x] & personOwn[y] == 00000000000000000000000000000001 表示x,y共同拥有第0类物品,所以它们只有1类相同物品
// personOwn[x] ^ personOwn[y] == 00000000000000000000000000001110 表示x,y第1、2、3类物品拥有情况不同,所以它们有3类不同物品
// personOwn[x] | personOwn[y] == 00000000000000000000000000001111 表示x,y一共拥有第0、1、2、3物品,所以它们一共有4类物品。
return
}
// 计算 a 有多少个1
func countOne(a int) int {
count := 0
for a != 0 {
count++
a = a & (a - 1)
}
return count
}
3.8 快速幂
// 快速幂,计算a^n,时间复杂度O(logn)
func quickPow(a, n int) int {
sum := 1
for n != 0 {
if n&1 == 1 {
sum *= a
}
a = a * a
n >>= 1
}
return sum
}
4. 注意
- 以上位运算基于补码。