【力扣】338. 比特位计数

以下为力扣官方题解

题目

给定一个非负整数 n u m num num。对于 0 ≤ i ≤ n u m 0 ≤ i ≤ num 0inum 范围中的每个数字 i i i ,计算其二进制数中的 1 1 1 的数目并将它们作为数组返回。

示例 1:

输入: 2 2 2
输出: [ 0 , 1 , 1 ] [0,1,1] [0,1,1]

示例 2:

输入: 5 5 5
输出: [ 0 , 1 , 1 , 2 , 1 , 2 ] [0,1,1,2,1,2] [0,1,1,2,1,2]

进阶:

  1. 给出时间复杂度为 O ( n ∗ s i z e o f ( i n t e g e r ) ) O(n*sizeof(integer)) O(nsizeof(integer)) 的解答非常容易。但你可以在线性时间 O ( n ) O(n) O(n) 内用一趟扫描做到吗?
  2. 要求算法的空间复杂度为 O ( n ) O(n) O(n)
  3. 你能进一步完善解法吗?要求在 C + + C++ C++ 或任何其他语言中不使用任何内置函数(如 C + + C++ C++ 中的 KaTeX parse error: Expected group after '_' at position 1: _̲_builtin_popcou…)来执行此操作。

官方题解

前言

这道题需要计算从 0 0 0 n u m num num 的每个数的二进制表示中的 1 1 1 的数目。最直观的方法是对每个数直接计算二进制表示中的 1 1 1 的数目,时间复杂度较高。也可以使用动态规划的方法,时间复杂度较低。

为了表述简洁,下文用「一比特数」表示二进制表示中的 1 1 1 的数目。

方法一:直接计算

最直观的方法是对从 0 0 0 n u m num num 的每个数直接计算「一比特数」。

每个 i n t int int 型的数都可以用 32 32 32 位二进制数表示,只要遍历其二进制表示的每一位即可得到 1 1 1 的数目。

利用位运算的技巧,可以在一定程度上提升计算速度。按位与运算( & \& &)的一个性质是:对于任意整数 x x x,令 x = x & ( x − 1 ) x=x \&(x-1) x=x&(x1),该运算将 x x x 的二进制表示的最后一个 1 1 1 变成 0 0 0。因此,对 x x x 重复该操作,直到 x x x 变成 0 0 0,则操作次数即为 x x x 的「一比特数」。

另外,部分编程语言有相应的内置函数,例如 J a v a Java Java I n t e g e r . b i t C o u n t Integer.bitCount Integer.bitCount C + + C++ C++KaTeX parse error: Expected group after '_' at position 1: _̲_builtin_popcou… G o Go Go b i t s . O n e s C o u n bits.OnesCoun bits.OnesCoun 等,读者可以自行尝试。需要注意的是,使用编程语言的内置函数时,不适用本方法的时间复杂度分析。

代码

class Solution {
    public int[] countBits(int num) {
        int[] bits = new int[num+1];
        for (int i=0; i<=num; i++)
        {
            bits[i] = countOnes(i);
        }
        return bits;
    }

    public int countOnes(int x) {
        int ones = 0;
        while (x > 0)
        {
            x &= (x-1);
            ones ++;
        }
        return ones;
    }
}

复杂度分析

  • 时间复杂度: O ( k × num ) O(k \times \textit{num}) O(k×num),其中 k k k i n t int int 型的二进制位数, k = 32 k=32 k=32。需要对从 0 0 0 n u m num num 的每个数使用 O ( k ) O(k) O(k) 的时间计算「一比特数」,因此时间复杂度是 O ( k × num ) O(k \times \textit{num}) O(k×num)
  • 空间复杂度: O ( 1 ) O(1) O(1)。除了返回的数组以外,空间复杂度为常数。

方法二:动态规划——最高有效位

方法一需要对每个数遍历其二进制表示的每一位。可以换一个思路,当计算 i i i 的「一比特数」时,如果存在 0 ≤ j < i 0 \le j<i 0j<i j j j 的「一比特数」已知,且 i i i j j j 相比, i i i 的二进制表示只多了一个 1 1 1,则可以快速得到 i i i 的「一比特数」。

令 $bits[i]$ 表示 i i i 的「一比特数」,则上述关系可以表示成: b i t s [ i ] = b i t s [ j ] + 1 bits[i]=bits[j]+1 bits[i]=bits[j]+1

对于正整数 x x x,如果可以知道最大的正整数 y y y,使得 y ≤ x y \le x yx y y y 2 2 2 的整数次幂,则 y y y 的二进制表示中只有最高位是 1 1 1,其余都是 0 0 0,此时称 y y y x x x 的「最高有效位」。令 z = x − y z=x−y z=xy,显然 0 ≤ z < x 0 \le z<x 0z<x,则 b i t s [ x ] = b i t s [ z ] + 1 bits[x]=bits[z]+1 bits[x]=bits[z]+1

为了判断一个正整数是不是 2 2 2 的整数次幂,可以利用方法一中提到的按位与运算的性质。如果正整数 y y y 2 2 2 的整数次幂,则 y y y 的二进制表示中只有最高位是 1 1 1,其余都是 0 0 0,因此 y & ( y − 1 ) = 0 y \&(y-1)=0 y&(y1)=0。由此可见,正整数 y y y 2 2 2 的整数次幂,当且仅当 y & ( y − 1 ) = 0 y \&(y-1)=0 y&(y1)=0

显然, 0 0 0 的「一比特数」为 0 0 0。使用 h i g h B i t highBit highBit 表示当前的最高有效位,遍历从 1 1 1 n u m num num 的每个正整数 i i i,进行如下操作。

  • 如果 i & ( i − 1 ) = 0 i \&(i-1)=0 i&(i1)=0,则令 h i g h B i t = i highBit=i highBit=i,更新当前的最高有效位。
  • i i i i − h i g h B i t i−highBit ihighBit 的「一比特数」多 1 1 1,由于是从小到大遍历每个数,因此遍历到 i i i 时, i − h i g h B i t i−highBit ihighBit 的「一比特数」已知,令 b i t s [ i ] = b i t s [ i − h i g h B i t ] + 1 bits[i]=bits[i−highBit]+1 bits[i]=bits[ihighBit]+1

最终得到的数组 b i t s bits bits 即为答案。

代码

class Solution {
    public int[] countBits(int num) {
        int[] bits = new int[num+1];
        int highBit = 0;
        for (int i=1; i<=num; i++)
        {
            if ((i&(i-1)) == 0)
            {
                highBit = i;
            }
            bits[i] = bits[i-highBit] + 1;
        }
        return bits;
    }
}

复杂度分析

  • 时间复杂度: O ( num ) O(\textit{num}) O(num)。对于每个数,只需要 O ( 1 ) O(1) O(1) 的时间计算「一比特数」。
  • 空间复杂度: O ( 1 ) O(1) O(1)。除了返回的数组以外,空间复杂度为常数。

方法三:动态规划——最低有效位

方法二需要实时维护最高有效位,当遍历到的数是 2 2 2 的整数次幂时,需要更新最高有效位。如果再换一个思路,可以使用「最低有效位」计算「一比特数」。

对于正整数 x x x,将其二进制表示右移一位,等价于将其二进制表示的最低位去掉,得到的数是 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor 2x。如果 bits [ ⌊ x 2 ⌋ ] \textit{bits}\big[\lfloor \frac{x}{2} \rfloor\big] bits[2x] 的值已知,则可以得到 b i t s [ x ] bits[x] bits[x] 的值:

  • 如果 x x x 是偶数,则 bits [ x ] = bits [ ⌊ x 2 ⌋ ] \textit{bits}[x]=\textit{bits}\big[\lfloor \frac{x}{2} \rfloor\big] bits[x]=bits[2x]
  • 如果 x x x 是奇数,则 bits [ x ] = bits [ ⌊ x 2 ⌋ ] + 1 \textit{bits}[x]=\textit{bits}\big[\lfloor \frac{x}{2} \rfloor\big]+1 bits[x]=bits[2x]+1

上述两种情况可以合并成: b i t s [ x ] bits[x] bits[x] 的值等于 bits [ ⌊ x 2 ⌋ ] \textit{bits}\big[\lfloor \frac{x}{2} \rfloor\big] bits[2x] 的值加上 x x x 除以 2 2 2 的余数。

由于 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor 2x 可以通过 x > > 1 x>>1 x>>1 得到, x x x 除以 2 2 2 的余数可以通过 x & 1 x \& 1 x&1 得到,因此有: bits [ x ] = bits [ x > > 1 ] + ( x & 1 ) \textit{bits}[x]=\textit{bits}[x>>1]+(x \& 1) bits[x]=bits[x>>1]+(x&1)

遍历从 1 1 1 n u m num num 的每个正整数 iii,计算 b i t s bits bits 的值。最终得到的数组 b i t s bits bits 即为答案。

代码

class Solution {
    public int[] countBits(int num) {
        int[] bits = new int[num+1];
        for (int i=1; i<=num; i++) 
        {
            bits[i] = bits[i >> 1] + (i & 1);
        }
        return bits;
    }
}

复杂度分析

  • 时间复杂度: O ( n u m ) O(num) O(num)。对于每个数,只需要 O ( 1 ) O(1) O(1) 的时间计算「一比特数」。
  • 空间复杂度: O ( 1 ) O(1) O(1)。除了返回的数组以外,空间复杂度为常数。

方法四:动态规划——最低设置位

定义正整数 x x x 的「最低设置位」为 x x x 的二进制表示中的最低的 1 1 1 所在位。例如, 10 10 10 的二进制表示是 101 0 ( 2 ) 1010_{(2)} 1010(2),其最低设置位为 2 2 2,对应的二进制表示是 1 0 ( 2 ) 10_{(2)} 10(2)

y = x & ( x − 1 ) y=x \&(x-1) y=x&(x1),则 y y y 为将 x x x 的最低设置位从 1 1 1 变成 0 0 0 之后的数,显然 0 ≤ y < x 0 \le y<x 0y<x b i t s [ x ] = b i t s [ y ] + 1 bits[x]=bits[y]+1 bits[x]=bits[y]+1。因此对任意正整数 x x x,都有 bits [ x ] = bits [ x & ( x − 1 ) ] + 1 \textit{bits}[x]=\textit{bits}[x \&(x-1)]+1 bits[x]=bits[x&(x1)]+1

遍历从 1 1 1 n u m num num 的每个正整数 iii,计算 b i t s bits bits 的值。最终得到的数组 b i t s bits bits 即为答案。

代码

class Solution {
    public int[] countBits(int num) {
        int[] bits = new int[num+1];
        for (int i=1; i<=num; i++)
        {
            bits[i] = bits[i & (i-1)] + 1;
        }
        return bits;
    }
}

复杂度分析

  • 时间复杂度: O ( n u m ) O(num) O(num)。对于每个数,只需要 O ( 1 ) O(1) O(1) 的时间计算「一比特数」。
  • 空间复杂度: O ( 1 ) O(1) O(1)。除了返回的数组以外,空间复杂度为常数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值