leetcode 338.比特位计数

leetcode 338.比特位计数

题干

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 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计数,首先想到的肯定是c++的内置方法__builtin_popcount

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> ans;
        for(int i = 0 ; i <= num ; ++i){
            ans.push_back(__builtin_popcount(i));
        }
        return ans;
    }
};

尝试用动规嗯找了一波规律,结果发现找出来的规律只适用于num<=87的情况,GG

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> dp(num + 1,0);
        dp[0] = 0;
        int rangeBound = 2;
        for(int i = 1 ; i <= num ; ++i){
            if(i == rangeBound){
                dp[i] = 1;
                rangeBound *= 2;
                continue;
            }
            if(i % 2 == 1){
                dp[i] = dp[i - 1] + 1;
            }else{
                //偶数的情况
                if((i - 6) % 4 == 0){
                    //维持前一位的情况
                    dp[i] = dp[i - 1];
                }else{
                    //下降的情况,此时i一定是4的倍数,其中二幂的i已经被最开始的if短路了
                    //innerIndex用来标记该次下降在两个二幂之间属于偶数组中的第几组(4个数一组)
                    int innerIndex = (i - rangeBound / 2) / 4 + 1;
                    if(innerIndex % 2 == 1){
                        //如果组号是奇数,下降和组号有关
                        if(innerIndex <= rangeBound / 2 / 4 / 2 + 1){
                            //左半边的情况
                            dp[i] = dp[i - 1] - innerIndex / 2;
                        }else{
                            //右半边的情况
                            dp[i] = dp[i - 1] - (rangeBound / 2 / 4 - innerIndex + 1) / 2;
                        }
                        dp[i]--;
                    }else{
                        //如果组号是偶数,下降1
                        dp[i] = dp[i - 1] - 1;
                    }
                    
                }
            }
        }
        return dp;
    }
};

/*
    0 - 0
    1 - 1 -1
    2 - 10 - 1
    3 - 11 - 2
    4 - 100 - 1
    5 - 101 - 2
    6 - 110 - 2
    7 - 111 - 3
    8 - 1000 - 1
    9 - 1001 - 2
    10 - 1010 - 2
    11 - 1011 - 3
    12 - 1100 - 2
    13 - 1101 - 3
    14 - 1110 - 3
    15 - 1111 - 4
    16 - 10000 - 1
    17 - 10001 - 2
    18 - 10010 - 2
    19 - 10011 - 3
    20 - 10100 - 2
    21 - 10101 - 3
    22 - 10110 - 3
    23 - 10111 - 4
    24 - 11000 - 2
    25 - 11001 - 3
    26 - 11010 - 3
    27 - 11011 - 4
    28 - 11100 - 3
    29 - 11101 - 4
    30 - 11110 - 4
    31 - 11111 - 5
    32 - 100000 - 1
    33 - 100001 - 2
    34 - 100010 - 2
    35 - 100011 - 3
    36 - 100100 - 2
    37 - 100101 - 3
    38 - 100110 - 3
    39 - 100111 - 4
    40 - 101000 - 2
    41 - 101001 - 3
    42 - 101010 - 3
    43 - 101011 - 4
    44 - 101100 - 3
    45 - 101101 - 4
    46 - 101110 - 4
    47 - 101111 - 5
    48 - 110000 - 2
    49 - 110001 - 3
    50 - 110010 - 3
    51 - 110011 - 4
    52 - 110100 - 3
    53 - 110101 - 4
    54 - 110110 - 4
    55 - 110111 - 5
    56 - 111000 - 3
    57 - 111001 - 4
    58 - 111010 - 4
    59 - 111011 - 5
    60 - 111100 - 4
    61 - 111101 - 5
    62 - 111110 - 5
    63 - 111111 - 6
    64 - 1000000 - 1
    1223 2334 2334 3445 2334 3445 3445 4556
    12 23 / 23 34 / 23 34 / 34 45
      1   2   1   3   1   2   1 
*/

从之前找规律的动规失败中总结经验,问题主要出在下降时的情况,其实这里只要利用二进制的性质,就根不不用做出很多抽象的判断。
核心即是二进制减法
我们以2的幂为分界来把数字进行分组,拿[32,64)举例,32的二进制表示是100000。我们任取区间内任意数,比如58,58的二进制表示为111010,相较于32多了较低五位(11010)上的3个1,而两数二进制相差的部分11010十进制表示即为26,即58 - 32。
所以可以得到状态转移方程dp[i] = dp[rangeBound / 2 - i] + 1 此处rangeBound为数所在二幂区间的上界。
这个规律在区间内具有普适性,因此之前单独拿来判断的i为奇数时的情况也可以直接归入。
如此便可以得到:

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> dp(num + 1,0);
        dp[0] = 0;
        int rangeBound = 2;
        for(int i = 1 ; i <= num ; ++i){
            if(i == rangeBound){
                dp[i] = 1;
                rangeBound *= 2;
                continue;
            }
            dp[i] = dp[i - rangeBound / 2] + 1;
        }
        return dp;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值