• 力扣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/

  • 相关阅读:
    jsp学习笔记(一)
    20170215学习计划
    腾讯云CVM使用记录--使用root权限
    转:ASP.NET MVC3 Model验证总结
    ASP.NET MVC3更新出错:ObjectStateManager中已存在具有同一键的对象
    c#中如何将一个string数组转换为int数组
    转:自定义ASP.NET MVC Html辅助方法
    转:ASP.NET MVC扩展之HtmlHelper辅助方法
    自己用的一个ASP.Net MVC分页拿出来分享下(转)
    MVC分页控件之二,为IQueryable定义一个扩展方法,直接反回PagedList<T>结果集(转)
  • 原文地址:https://www.cnblogs.com/hi3254014978/p/12939972.html
Copyright © 2020-2023  润新知