思路:
一、暴力法、模拟运算
- public static int bitCount(int i) 可以直接计算int数二进制中1的个数
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i++) {
res[i] = Integer.bitCount(i);
}
return res;
}
- 自己实现bitCount(). 位运算可知 x & (x - 1) 可以让最靠后的 1 变成 0,注意,这里不是最后一位的 1 ,而是最靠后面的 1 变成 0,这样循环转化,过程中计数,直到数值变成 全 0 即 0
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i++) {
res[i] = bitCount(i);
}
return res;
}
public int countOnes(int x) {
int count = 0;
while (x > 0) {
x &= (x - 1);
count++;
}
return count;
}
二、递归
从 0 ~ num 推进的过程中,首先可知:
- 偶数 x 和 x / 2 即 x >> 1 的 二进制 1 个数相同,因为最后一位是 0,抹去最后一位不影响统计个数
- 奇数 x 和 x >> 1 的二进制 1 个数差 1,因为最后一位 1 被抹除 需要补上。这里也可以采用 x - 1 的 1 个数 + 1,道理都一样,最后一位 1 被抹去,要补上
所以:
x为奇数: res[i] = bitCountRecusion(x - 1) + 1
x为偶数:res[i] = bitCountRecursion(x >> 1)
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i++) {
res[i] = bitCountRecusion(i);
}
return res;
}
public int bitCountRecusion(int x) {
if (x == 0) return 0;
if (x % 2 == 1)
return bitCountRecusion(x - 1) + 1;
else
return bitCountRecusion(x >> 1);
}
三、记忆化搜索
在上面的递归方法中,重复了很多计算,比如 算 8 的 bitCount 时,一直要计算 4、2、1、0,所以添加一个memo数组来储存前面计算的bitCount,这样在下一次计算的时候可以被memo数组捕捉到,直接返回,提高递归效率。
if (memo[x] != 0) return memo[x]
public int[] countBits(int num) {
int[] res = new int[num + 1];
int[] memo = new int[num + 1];
for (int i = 0; i <= num; i++) {
res[i] = bitCountMemoSearch(i, memo);
}
return res;
}
public int bitCountMemoSearch(int x, int[] memo) {
if (x == 0) return 0;
if (memo[x] != 0) return memo[x]; // 记录中存在,直接返回
int res = 0;
if (x % 2 == 1)
res = bitCountMemoSearch(x >> 1, memo) + 1;
else
res = bitCountMemoSearch(x >> 1, memo);
memo[x] = res; // 记录当前值,便于下次记忆化查找
return res;
}
四、动态规划
上面的记忆化搜索过程中,memo仅在一次调用递归函数就可以捕捉到,意味着递归函数就无意义了,而memo记录数组也可以在查找前面的res[] 结果中 替代,这种思想就是动态规划。
- 判断奇偶数,然后查找前面记录的res值。
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i++) {
if (i % 2 == 0)
res[i] = res[i >> 1];
else
//res[i] = res[i - 1] + 1;
//也可以这么写,前一位是偶数,最后补一个低位的1就可以
res[i] = res[i >> 1] + 1;
}
return res;
}
- 不需要判断奇偶数的统一写法。最终版代码:
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i++) {
res[i] = res[i >> 1] + (i & 1);
}
return res;
}