前言
二进制计数可以直接基于分治去快速统计,如果是连续数的二进制计数,可以利用前面已经计算出的状态进行递推求解,即动态规划。
一、二进制计数
二、动态规划 & bitCount分治统计
1、bitCount分治统计
思想:每两位统一比特的个数,每四位统计比特的个数,依次类推,每十六位统计比特的个数。
bitCount分治统计源码解析,以及几处的细节优化。
func countBits(n int) []int {
rs := make([]int,n + 1)
for i := 1;i <= n;i++ {
rs[i] = bitCount(i)
}
return rs
}
func bitCount(n int) int {
// 两位二进制的规律
n = n - ((n >> 1) & 0x55555555)
n = (n & 0x33333333) + ((n >> 2) & 0x33333333)
// 最多八位二进制
n = (n + (n >> 4)) & 0x0f0f0f0f
// 最多16二进制,但即使是32位,8bit也足够表示了。
n = n + (n >> 8)
n = n + (n >> 16)
return n & 0x3f
}
2、动态规划
对于连续求解数的二进制,可以利用前面已经统计的比特情况,来快速递推当前的比特数情况。
容易想到,dp[i] = dp[i >> 1] + (i & 1)
// 动态规划
// 末尾为1,直接dp[i] = dp[i >> 1] + 1;末尾为0,dp[i] = d[i >> 1]
func countBits(n int) []int {
dp := make([]int,n + 1)
for i := 1;i <= n;i++ {
dp[i] = dp[i >> 1] + (i & 1)
}
return dp
}
总结
1)bitCount源码写的很好,好在分治思想,好在它挖掘了二进制而内的一些规律和限制,进行了多处优化,减少了多次位运算,提高性能。所以好在它的大思想+性能优化,好在它以提升性能为目的。
2)动态规划最重要的就是如何递推,有的简单的直接就看出来了,难的需要分析并分类合并递推公式。多练习才能做到一眼看出递推公式 + 分析分类合并递推公式的能力。
参考文献
[1] LeetCode 比特位计数
[2] bitCount源码解析