学习目标:
代码越怪,跑的越快。
我们常规面对一个问题,第一想法在绝大部分都不是最优的。时空间复杂度都很高,需要我们不断地寻找规律去优化。就像我们今天这个题,我相信绝大部分的朋友的第一想法一定是:定义一个函数用来求一个数二进制中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;
}
以上就是我们这道题的全部优化方法,都是基于动态规划的思想。有不同方法的朋友也可以在评论区留言交流。
记得点赞收藏,防止以后找不到哦!