力扣338. 比特位计数

 338. 比特位计数

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

示例 1:

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

示例 2:

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

进阶:
给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
要求算法的空间复杂度为O(n)。
你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。

思路一:

1. 遍历1-num的所有二进制

2. 使用布赖恩·克尼根算法,xor & (xor - 1)即可移除xor最右边的那个1,省去了移除0的过程,减少了迭代次数,直接删除最右边的1

 1 class Solution {
 2     public int[] countBits(int num) {
 3         // 遍历1-num的所有二进制
 4         int[] res = new int [num + 1];
 5         Arrays.fill(res, 0);
 6         for(int i = 0; i <= num; i++){
 7             res[i] = 0;
 8             int temp = i;
 9             while(temp != 0){
10                 res[i]++;
11                 temp = temp & (temp - 1);   // 使用布赖恩·克尼根算法,xor & (xor - 1)即可移除xor最右边的那个1,省去了移除0的过程,减少了迭代次数,直接删除最右边的1
12             }
13         }
14         return res;
15 
16     }
17 }

力扣测试时间需要3ms, 超过了39.70%的用户,效率不高

算法复杂度为:

时间复杂度为O(n*k), 对于每个数x, k表示x中1的个数

空间复杂度:O(n)。 我们需要 O(n) 的空间来存储计数结果。如果排除这一点,就只需要常数空间。

思路二:动态规划 + 最高有效位 

我们假设 b = 2^m, 那么b的第(m + 1)个比特位肯定为1, 小于(m+1)的比特位全为0, 如果一个小于b的数x, 那么这个数的第(m+1)为肯定为0, 那么(x + b)的结果就是在x的原来的二进制形式上把原来第(m+1)位上的0改成1, 所以(x + b) 的二进制形式的1的个数比x多一个,由此推出,f(x+b) = f(x) + 1, 当x< b时, 这其实是[0, b)到[b, 2b)的映射

 1 class Solution {
 2     public int[] countBits(int num) {
 3         int[] ans = new int[num + 1];
 4         int i = 0, b = 1;
 5         while(b <= num){
 6             while(i < b && i + b <= num){
 7                 ans[i + b] = ans[i] + 1;    // 将[0, b)映射到[b, 2b)
 8                 i++;
 9             }
10             i = 0;  // 重置i= 0;
11             b <<= 1;
12         }
13         return ans;
14     }
15 }

 力扣测试时间只需2ms, 超过了80.89%的用户,效率高于法一

 复杂度分析:

对ans[i]的每个元素进行了一次求值,所以时间复杂度为O(n)

空间复杂度为O(n), ans数组既作为动态规划的数组,又是存储结果的数组,所以这个数组是必须的。

思路三:动态规划 + 最低有效位 (效率最高)

f(x) = f(x/2) + (x&1), (x/2)就是将x右移一位,移掉的那位可能是0也可能是1, 用x和1做与运算即可判断那位是1还是0,动态规划就是用利用前面的计算结果得出后面的计算结果,f(x/2)肯定比f(x)先计算出来,保存在ans[x/2]中,所以f(x)的计算可以利用f(x/2)的取值来获得。

 1 class Solution {
 2     public int[] countBits(int num) {
 3         int[] ans = new int[num + 1];
 4         
 5         for(int i = 0; i <= num; i++){
 6             ans[i] = ans[i >> 1] + (i & 1);
 7         }
 8         return ans;
 9     }
10 }

力扣测试时间只需1ms, 超过了99.93%的用户,这个算法的效率最高

 复杂度分析:

对ans[i]的每个元素进行了一次求值,所以时间复杂度为O(n)

空间复杂度为O(n), ans数组既作为动态规划的数组,又是存储结果的数组,所以这个数组是必须的。

思路四:动态规划 + 最后设置位 (效率最高)

动态规划就是利用前面计算出来的结果退出后面的结果,所以如果我们可以通过前面已经计算出的f(x)退出后面的f(y)的话,就可以以O(n)的时间复杂度完成统计。这里寻找的x肯定必须小于y, 所以寻找的是(y&(y-1)), 这个数看起来很长,其实它就是y移除一个最右边的1的结果,所以这个值肯定比y小,且f(y)和f(y&(y-1))刚好相差一,很容易就由前面的值推出了后面的值。

f(x) = f(x&(x-1)) + 1

 1 class Solution {
 2     public int[] countBits(int num) {
 3         int[] ans = new int[num + 1];
 4         
 5         for(int i = 1; i <= num; i++){
 6             ans[i] = ans[i & (i - 1)] + 1;
 7         }
 8         return ans;
 9     }
10 }

力扣测试时间只需1ms, 超过了99.93%的用户,算法效率等同于法三

复杂度分析:

对ans[i]的每个元素进行了一次求值,所以时间复杂度为O(n)

空间复杂度为O(n), ans数组既作为动态规划的数组,又是存储结果的数组,所以这个数组是必须的。

思路来源:

https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值