前 n 个数字二进制中 1 的个数

222 阅读1分钟

「这是我参与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;
}