「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」
前言
笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。
系列文章收录《算法》专栏中。
问题描述
给定一个非负整数 n 请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。
示例 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
说明 :
0 <= n <= 105
进阶:
- 给出时间复杂度为 O(n*sizeof(integer)) 的解答非常容易。但你可以在线性时间 O(n) 内用一趟扫描做到吗?
- 要求算法的空间复杂度为 O(n) 。
- 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount )来执行此操作。
确定学习目标
对《前 n 个数字二进制中 1 的个数》的算法过程进行剖析。
剖析
题目给我的第一反应就是,使用Integer.toString把整数转成二进制字符串,在遍历0到n的过程中,遍历每个数的二进制字符串的每个字符是否和‘1’匹配,匹配的话加1,如下代码:
public static int[] zeroToNOneCount(int n) {
if (n < 0 || n > Math.pow(10, 5)) {
throw new RuntimeException("n的范围为:0 <= n <= 10^5");
}
int[] oneCountArry = new int[n + 1];
for (int i = 0; i <= n; i++) {
String iToBinaryStr = Integer.toString(i, 2);
int oneCount = 0;
for (int j = 0; j < iToBinaryStr.length(); j++) {
if (iToBinaryStr.charAt(j) == '1') {
oneCount++;
}
}
oneCountArry[i] = oneCount;
oneCount = 0;
}
return oneCountArry;
}
很简单,但是比较暴力,我们可以再想想怎么利用二进制位运算把1一个一个消灭掉,设一个整数为n,n &= (n - 1)就可以消灭掉最后一个1,每次这么做直到n变成0,如下面代码:
public static int[] zeroToNOneCount1(int n) {
if (n < 0 || n > Math.pow(10, 5)) {
throw new RuntimeException("n的范围为:0 <= n <= 10^5");
}
int[] oneCountArry = new int[n + 1];
for (int i = 0; i <= n; i++) {
int j = i;
int oneCount = 0;
while (j > 0) {
j &= (j - 1);
oneCount++;
}
oneCountArry[i] = oneCount;
oneCount = 0;
}
return oneCountArry;
}