持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情
动态规划(Dynamic Programming)是一种分阶段求解决策问题的数学思想,它通过把原问题分解为简单的子问题来解决复杂问题。
比特位计数
给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
示例 1:
输入:n = 2 输出:[0,1,1] 解释: 0 --> 0 1 --> 1 2 --> 10
示例 2:
输入:n = 5 输出:[0,1,1,2,1,2] 解释: 0 --> 0 1 --> 1 2 --> 10 3 --> 11 4 --> 100 5 --> 101
动态规划
思路:
对于所有的数字,只有两类:
-
奇数:二进制表示中,奇数一定比前面那个偶数多一个 1,因为多的就是最低位的 1。 举例: 0 = 0 1 = 1 2 = 10 3 = 11
-
偶数:二进制表示中,偶数中 1 的个数一定和除以 2 之后的那个数一样多。因为最低位是 0,除以 2 就是右移一位,也就是把那个 0 抹掉而已,所以 1 的个数是不变的。 举例: 2 = 10 4 = 100 8 = 1000
3 = 11 6 = 110 12 = 1100
初始化 dp[0] = 0;
fun countBits(n: Int): IntArray {
val res = IntArray(n + 1)
res[0] = 0
for (i in 1..n) {
if (i and 1 != 0) //奇数
res[i] = res[i - 1] + 1 else res[i] = res[i / 2]
}
return res
}
Brian Kernighan 算法
最直观的做法是对从 0 到 n 的每个整数直接计算「一比特数」。每个int 型的数都可以用 32 位二进制数表示,只要遍历其二进制表示的每一位即可得到 1 的数目。
Brian Kernighan 算法的原理是:对于任意整数 x,令 x=x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。
对于给定的 n,计算从 0 到 n 的每个整数的「一比特数」的时间都不会超过O(logn),因此总时间复杂度为 O(nlogn)。
fun countBits(n: Int): IntArray {
val bits = IntArray(n + 1)
for (i in 0..n) {
bits[i] = countOnes(i)
}
return bits
}
fun countOnes(x1: Int): Int {
var x = x1
var ones = 0
while (x > 0) {
x = x and x - 1
ones++
}
return ones
}