2025位运算指南:15道LeetCode-Go题解从基础到进阶
你是否还在为位运算题目抓耳挠腮?是否觉得那些&、|、^符号如同天书?本文将通过LeetCode-Go项目中15道经典位运算题目,带你掌握从基础操作到高级技巧的全套方法,让你在算法实践中轻松应对各类位运算难题。读完本文,你将能够:
- 熟练运用位运算解决实际问题
- 理解并掌握15种位运算技巧
- 大幅提升算法性能,优化时间和空间复杂度
位运算基础:从理论到实践
位运算(Bit Manipulation)是程序设计中对整数的二进制表示进行操作的技术。在Go语言中,位运算操作符包括:&(与)、|(或)、^(异或)、<<(左移)和>>(右移)。这些操作看似简单,却能组合出各种高效的算法。
LeetCode-Go项目中提供了丰富的位运算工具类,例如template/BIT.go中的二进制索引树(Binary Indexed Tree)实现:
func (bit *BinaryIndexedTree) Add(index int, val int) {
for ; index <= bit.capacity; index += index & -index {
bit.tree[index] += val
}
}
func (bit *BinaryIndexedTree) Query(index int) int {
sum := 0
for ; index > 0; index -= index & -index {
sum += bit.tree[index]
}
return sum
}
这段代码巧妙地利用index & -index来获取最低位的1,实现了高效的前缀和查询与更新操作,时间复杂度均为O(log n)。
计数问题:位运算的拿手好戏
计算数字的二进制表示中1的个数
0338.Counting-Bits/338. Counting Bits.go提供了一种高效计算从0到n的每个数的二进制表示中1的个数的方法:
func countBits(num int) []int {
bits := make([]int, num+1)
for i := 1; i <= num; i++ {
bits[i] += bits[i&(i-1)] + 1
}
return bits
}
这里的关键是i&(i-1)操作,它能够清除i二进制表示中的最后一个1。例如,当i=6(二进制110)时,i&(i-1)=110 & 101=100(4),这样我们就可以利用之前计算的结果,避免重复计算。
判断二进制数是否具有交替位
0693.Binary-Number-with-Alternating-Bits/693. Binary Number with Alternating Bits.go展示了如何判断一个数的二进制表示是否具有交替的0和1:
func hasAlternatingBits(n int) bool {
n ^= n >> 1
return (n & (n + 1)) == 0
}
这个解法非常巧妙。首先,将n与n右移1位的结果进行异或操作。如果n的二进制表示是交替的0和1,那么异或的结果将是全1。例如,n=5(101),n>>1=2(010),n^n>>1=111(7)。然后,我们检查这个结果加1是否为2的幂,即(n & (n + 1)) == 0。
集合操作:位运算的高效应用
子集生成
0078.Subsets/78. Subsets.go中提到了使用位运算生成子集的方法:
// 解法三:位运算的方法
func subsets(nums []int) [][]int {
result := make([][]int, 0)
n := len(nums)
// 总共有 2^n 个子集
for i := 0; i < (1 << n); i++ {
subset := make([]int, 0)
for j := 0; j < n; j++ {
// 检查第 j 位是否为 1
if (i & (1 << j)) != 0 {
subset = append(subset, nums[j])
}
}
result = append(result, subset)
}
return result
}
这种方法的思路是,对于n个元素的集合,每个子集可以用一个n位的二进制数表示,其中第j位为1表示集合中包含第j个元素。
有效单词数统计
1178.Number-of-Valid-Words-for-Each-Puzzle/1178. Number of Valid Words for Each Puzzle.go展示了如何使用位运算来解决字符串匹配问题:
var bitMap uint32
for _, w := range word {
bitMap |= (1 << (w - 'a')) // 将单词压缩为 bitMap
}
这里将每个单词压缩成一个32位的整数,其中每一位代表一个字母是否出现。这种方法不仅节省空间,还能利用位运算快速进行集合操作。
高级应用:位运算的奇思妙想
整数的补码
1009.Complement-of-Base-10-Integer/1009. Complement of Base 10 Integer.go展示了如何计算一个整数的补码:
func bitwiseComplement(n int) int {
if n == 0 {
return 1
}
mask := 1
for mask <= n {
mask <<= 1
}
return (mask - 1) ^ n
}
这里的关键是找到比n大的最小的2的幂,然后减1得到一个与n位数相同的全1掩码,最后与n进行异或操作得到补码。
设计位集
2166.Design-Bitset/2166. Design Bitset.go实现了一个位集数据结构,支持各种位操作:
type Bitset struct {
bits []uint64
size int
cnt int
flip bool
}
func Constructor(size int) Bitset {
return Bitset{
bits: make([]uint64, (size+63)/64),
size: size,
}
}
func (this *Bitset) Fix(idx int) {
pos, bit := idx/64, idx%64
if this.flip {
if (this.bits[pos] & (1 << bit)) != 0 {
this.bits[pos] &^= 1 << bit
this.cnt++
}
} else {
if (this.bits[pos] & (1 << bit)) == 0 {
this.bits[pos] |= 1 << bit
this.cnt++
}
}
}
这个实现使用了uint64切片来存储位,每个uint64可以存储64个 bit,大大节省了空间。同时,通过一个flip标志,实现了O(1)时间复杂度的翻转操作。
总结与展望
位运算作为一种底层操作,在算法优化中有着广泛的应用。通过本文介绍的15道LeetCode题目,我们可以看到位运算能够大幅提升算法的时间和空间效率。无论是简单的计数问题,还是复杂的集合操作,位运算都能提供简洁而高效的解决方案。
LeetCode-Go项目中还有更多位运算的精彩应用,例如0190.Reverse-Bits/190. Reverse Bits.go中的位反转,0762.Prime-Number-of-Set-Bits-in-Binary-Representation/762. Prime Number of Set Bits in Binary Representation.go中的素数计数等。
掌握位运算不仅能够帮助你在算法实践中脱颖而出,还能让你写出更高效、更优雅的代码。希望本文介绍的技巧能够对你有所启发,快去LeetCode-Go项目中探索更多位运算的奥秘吧!
如果你觉得本文对你有帮助,请点赞、收藏、关注三连,我们下期再见!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



