题目
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数
思路
暴力法
- 对于1~n中的每个数组,我们可以单独计算其1的个数,然后将其加起来
- 怎么计算?每次用
i &(i-1)
将整数i的最右边的1变成0,直到i变为0为止,看变了多少次,就有多少个1
-
例如:要计算101010有多少个1
- 先将其和
101001(101010 - 1)
做与运算,101010 & 101001 = 101000
- 再将
101000
和100111 (101000 - 1)
做与运算,结果为100000
- 最后将
100000
和011111
(100000 - 1)做与运算,结果为000000
- 先将其和
- 由于进行了3次运算,结果为1个1
-
时间复杂度:
- 如果整数有k位,则其1的个数的数量级为O(K),整体时间复杂度为
O(nK)
- 如果整数有k位,则其1的个数的数量级为O(K),整体时间复杂度为
动态规划法
- 我们还是从小到大依次计算每个数
i
中1的个数,放到dp[i]
中
-
但是每个数1的个数可以基于以前的数来计算,根据前面的分析可知,
i &(i-1)
将i的二进制形式中最右边的1变成0,也就是说,整数i的二进制形式中1的个数比i &(i-1)
的二进制形式中1的个数多1- 因此可以简单地计算出
dp[i] = dp[i &(i-1)]
- 为什么该做法可行?因为比i小的数,已经在之前计算过了,所以这里可以直接使用
- 因此可以简单地计算出
- 除此之外,还可以发现 i 除了最后一位,其他位置和 i >> 1 的数相同,因此1的个数也相同,因此可以得出
dp[i] = dp[i >> 1] + (i & 1)
,即利用dp >> 1中1的个数,加在上i多出来的那一位是否是1,如果是就加上1
代码
动态规划1
public int[] countBits(int n) {
int[] res = new int[n+ 1];
for (int i = 1;i<=n;i++) {
res[i] = res[i & (i - 1)] + 1;
}
return res;
}
动态规划2
public int[] countBits(int n) {
int[] res = new int[n+ 1];
for (int i = 1;i<=n;i++) {
res[i] = res[i >> 1] + (i & 1);
}
return res;
}