剑指offer|| 003.前n个数字二进制中1的个数

面试题3:前n个数字二进制中1的个数

力扣链接:https://leetcode-cn.com/problems/w3tCBm/

题目描述

给定一个非负整数n,请计算0到n之间的每个数字的二进制表示中1的个数,并输出一个数组。
0 <= n <= 105

样例输入1

n = 2

样例输出1

[0,1,1]
解释:
0 ----> 0
1 ----> 1
2 ----> 10

样例输入2

n = 5

样例输出2

[0,1,1,2,1,2]
解释
0 ----> 0
1 ----> 1
2 ----> 10
3 ----> 11
4 ----> 100
5 ----> 101


解析

最先想到的方法应该就是:使用一个for循环计算从0到n的每个整数的二进制形式中1的个数。于是问题就转化为如何求解一个整数二进制形式中1的个数。

第一种方法

用“i & (i-1)” 将整数最右边的1变成0。
"i-1"会把整数i最右边的1变成0,如果它的右边还有0,则右边的所有0都变成1,左边的所有位都保持不变。以二进制的1100为例,它减去1的结果是1011。1100和1011的位运算是1000。二进制的1100最右边的1变成0,结果刚好就是1000;

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> res(n+1,0);
        for(int i=0;i<=n;i++){
            int j=i;
            while(j!=0){
                res[i]++;
                j=j&(j-1);//去掉最右边的1
            }
        }
        return res;
    }
};

第二种方法

用“i&1”判断整数i的最后一个位置是0还是1,每判断一次就让"i>>1",消去刚刚判断过的那一位,重复上述过程直到i变成0;

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> res(n+1,0);
        for(int i=0;i<=n;i++){
            int j=i;
            while(j!=0){
                if(j&1)res[i]++;
                j>>=1;
            }
        }
        return res;
    }
};

不管这两种方法用哪种方法,时间复杂度都是O(nk)其中k是整数二进制形式中1的个数。


再仔细一点就会发现其实我们做了很多的重复工作。比如,在第一种方法中,因为“i&(i-1)”是把整数i的最后一个1变成0,所以在计算i中有多少个1的时候可以先计算出"i&(i-1)"中有多少个1然后再加上1;第二种方法,可以先计算出i>>1中有多少个1,然后在判断i最后一个位置是不是1。对于上述的情况,可以用一个数组保存前面算过的结果。

第一种方法的优化

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> res(n+1);
        res[0]=0;
        for(int i=1;i<=n;i++){
            res[i]=res[i&(i-1)]+1;
        }
        return res;
    }
};

第二种方法的优化

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> res(n+1);
        res[0]=0;
        for(int i=1;i<=n;i++){
            res[i]=res[i>>1]+(i&1);
        }
        return res;
    }
};

需要注意的就是,按位与&的优先级没有加法+的优先级高,所以需要把i&1加上括号



版权声明:本文为weixin_46290302原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。