每日一题——前n个数字二进制中一的个数(高效算法)

学习目标:

代码越怪,跑的越快。

我们常规面对一个问题,第一想法在绝大部分都不是最优的。时空间复杂度都很高,需要我们不断地寻找规律去优化。就像我们今天这个题,我相信绝大部分的朋友的第一想法一定是:定义一个函数用来求一个数二进制中1的个数,然后对数字从0到n遍历,数组存储每一个数二进制中1的个数。没错小编的第一想法也是这样。但是时间复杂度和空间复杂度是否较大,我们能否想出一个更忧的算法?


学习内容:

我们先看一下题目:

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

按照我们最初的那个想法,代码如下:

int num1(int n){
    int ans=0;
    while(n>0){
        if(n&1){
            ans++;
        }
        n/=2;
    }
    return ans;
}
int* countBits(int n, int* returnSize){
    *returnSize=n+1;
    int* dp=malloc(sizeof(int)*(*returnSize));
    int i=0;
    while(i<=n){
        dp[i]=num1(i);
        i++;
    }
    return dp;
}

时间复杂度是O(n*log n) ,每一次判断n的1的个数,都需要log n的时间复杂度。我们能否想出一个时间复杂度为常数的算法。


优化一: 

 当计算 i 的二进制1的个数时,如果存在 0<j<i<=n,如果 j 的二进制中1的个数已知,且i和j相比,i 的二进制表示只多了一个 1,则可以快速得到 i 的值。

对于正整数 x,如果可以知道最大的正整数 y,使得y<x 且y是2的整数次幂,则y的二进制表示中只有最高位是1,其余都是 0,此时称 y为x的「最高有效位」。令 z=x-y,则num[x]=num[z]+1;

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

显然,num[0]=0。使用h 表示当前的最高有效位,遍历从1 到 n的每个正整数i,进行如下操作。

如果i & (i−1)=0,则令 h=i,更新当前的最高有效位。

    bits[0] = 0;
    int highBit = 0;
    for (int i = 1; i <= n; i++) {
        if ((i & (i - 1)) == 0) {
            highBit = i;
        }
        bits[i] = bits[i - highBit] + 1;
    }

 刚才的是通过记录最前面一位的bits值,那么我们是不是也可以通过记录最后一位的值呢?那又应该怎么办呢?

优化二: 

当i向右移一位时,相当于除2,若i是偶数,则num[i]=num[i>>1],若i是奇数,则num[i]=num[i>>1]+1,这样我们就可以这样写num[i]=num[i>>1]+(i&1).

int* countBits(int n, int* returnSize){
    *returnSize=n+1;
    int* num=malloc(sizeof(int*)*(n+1));
    int i,h=0;
    for(i=0;i<=n;i++){
        num[i]=0;
    }
    num[0]=0;
    for(i=1;i<=n;i++){
        num[i]=num[i>>1]+(i&1);
    }
    return num;
}

优化三: 

还可以通过i&(i-1),这样是将i的最后一个1去掉,然后在num[i]=num[i&(i-1)]+1.

bits[0] = 0;
    for (int i = 1; i <= n; i++) {
        bits[i] = bits[i & (i - 1)] + 1;
    }

以上就是我们这道题的全部优化方法,都是基于动态规划的思想。有不同方法的朋友也可以在评论区留言交流。

记得点赞收藏,防止以后找不到哦!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的代码no摆烂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值