1、计算一个二进制数中1的个数
如给定一个整型数 x = 10,它的二进制表达式是(1010)B,题意就是计算出x的二进制表示中一的个数是2。
分析如下:
如果将x转化为二进制,就可以计算出1的个数了,此时复杂度是o(log n)的,复杂度还可以。
int func(int x){
int count = 0;
while(x){
if(x%2 == 1) count++;
x /= 2;
}
return count;
}
但是如果要继续降低复杂度,那就是常数级别的了,想想都很牛逼,确实,这个算法真实存在。
分析如下:
以x = 10为例,二进制表示(1010)B
x x-1 x&(x-1)
1010 1001 1000
因为减1就是最低位的那个1借位的,所以当x&(x-1)自然就去掉了一个想要计算的1,如此循环,直到x==0,也就算完了所有的1。真是妙!
x x-1 x&(x-1) count
1010 1001 1000 1
1000 0111 0000 2int func2(int x){
int count = 0;
while(x){
x = x & (x - 1);
count ++;
}
return count;
}
此时的复杂度就是o(m),m为x中1的个数。显然这个算法的复杂度更低。当二进制数中1的个数比较稀疏的时候,效率尤其高。
2、已知有随机生成1和7之间数的函数,设计生成1到10之间随机数的函数
乍一看,觉得很简单,也就是rand7()/7*10,但是这样做了以后会发现,有些数字是不可能出现的,也就是这个算法导致各个数出现的概率不均匀了。
分析:
运算的目的在于产生均匀分布的1-10,可是我们只有1-7,所以就要相办法产生数8-10,并且要求均匀。
这里提出一种叫做拒绝采样的方法。主要的思想是如果产生的随机数是目标范围内的,就直接返回,如果不是目标范围内的数,就丢弃,重新采样。
只要保证目标范围内的数是均匀分布的,就达到目的了。
均匀分布构造如下:[ rand7() + ( rand7() - 1 ) * 7 ] % 10 + 1
1 2 3 4 5 6 7 1 1 2 3 4 5 6 7 2 8 9 10 1 2 3 4 3 5 6 7 8 9 10 1 4 2 3 4 5 6 7 8 5 9 10 1 2 3 4 5 6 6 7 8 9 10 1 2 7 3 4 5 6 7 8 9
1 2 3 4 5 6 7 1 1 2 3 4 5 6 7 2 8 9 10 1 2 3 4 3 5 6 7 8 9 10 1 4 2 3 4 5 6 7 8 5 9 10 1 2 3 4 5 6 6 7 8 9 10 * * 7 * * * * * * *如上,首先产生的是49个数,这49个数显然不能保证概率的均匀性,但是前40个数据能保证是均匀分布的,因此可以舍弃后9个数据。
这样舍弃后的数据就是均匀分布的40个数据了。
int rand10() {
int idx;
do {
idx = rand7() + ( rand7() - 1 ) * 7;
} while (idx > 40);
return 1 + (idx - 1) % 10;
}