《剑指offer》【面试题10:二进制中1的个数】

面试题10:二进制中1的个数

我们在日常生活中用到的是十进制,所以一开始接受其他进制都感觉很不习惯;在程序员圈子里有一个流传了很久的笑话,说世界上一共有10种人,一种人知道二进制,而另一种人不知道二进制……

说起二进制,就要提到与之形影不离的位运算了,位运算总共有五种运算:与、或、异或、左移和右移。
下图是与、或、异或的运算规律:

左移运算符m<<n表示把m左移n位。左移n位的时候,最左边的n位将被丢弃掉,同时在最右边补上n个0.
例如:
00001010<<2 =  00101000
10001010<<3 =  01010000
右移运算符m>>n表示把m右移n位。右移n位的时候,最右边的n位将被丢弃。但右移分两种情况:第一种:如果数字是一个无符号的数值,则最左边的n位用0填补;另一种情况是:如果数字是一个有符号的数值,则用数字的符号位填补最左边的n位。
总结:数字是正数,右移移动n位,最左边补n位0;数字是负数,则右移之后在最左边补n个1.
例如:
00001010>>2  =  00000010
10001010>>3  =  11110001


面试题10: 题目: 请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把  9表示成二进制是1001,有2位是1.因此如果输入9,该函数输出2。

第一种算法:
思路:先判断整数二进制最右边的一位是不是1,是1,则计数器++,并进行右移,;是0则直接进行右移,直到这个整数的值为0;

代码:
//不好,可能会引起死循环,不是理想的
int NumberOf1(int n)//形参设置成unsigned int类型可以避免死循环
{
	int count = 0;
	while(n)
	{
		if(n & 1)
		{
			count++;
		}
		n = n >> 1;
	}
	return count;
}
缺点:当这个数为负数时,每次进行右移的时候,左边会进行补符号位操作,也就是1,当全部变成1的时候,就成为了死循环,这不是我们想要看到的,所以上述代码只适合形参为正数的计算。

第二种算法:
思路:定义一个变量flag值为1,首先让它与形参进行与运算,如果为真则计数器++,并且flag变量左移一位,即到了第二位,继续进行相同的操作;若为假,则直接左移一位,直到flag变量为0,也就是移动了和形参相同的位数,最后变量所有的位全为0,则运算结束。

代码:
//常规算法:输入的整数转化成二进制有多少位,就需要循环多少次
int Num_find_1(int n)
{
	int count = 0;
	unsigned int flag = 1;
	
	while(flag)
	{
		if(n & flag)
		{
			count++;
		}
		flag = flag << 1;
	}
	return count;
}
缺点:输入的整数转化成二进制有多少位,就需要循环多少次,效率不是很高。


第三种算法:
思路:利用把形参这个整数减去1,再和原整数做与运算,这样每次都会把整数最右边的一个0变成0;并且形参转化成二进制中有几个1,则执行与之对应的次数,相比较前面两种而言,不担心正数和负数的特殊情况,效率也明显得到了提高;

图解:


代码:

int Num_of_1(int n)
{
	int count = 0;
	while(n)
	{
		count++;
		n = (n-1) & n;
	}
	return count;
}
int main()
{
/*------------------------计算二进制中1的个数普通方法测试用例-----------------------*/
	/*出现死循环的测试,传入一个负数会出现死循环这种可能,所以形参设置成unsigned,
	可以避免这种情况*/
	
	//cout<<NumberOf1 (-1) <<endl;//死循环


/*------------------------计算二进制中1的个数常规算法测试用例-----------------------*/
	
	//cout<<Num_find_1(-1) <<endl;//效率不是很好,时间复杂度O(n)
	

/*------------------------计算二进制中1的个数完美算法测试用例-----------------------*/

	//正数:n为1
	cout<<"二进制中1的个数为: "<<Num_of_1(1)<<endl;
	//正数:n为0x7FFFFFFF
	cout<<"二进制中1的个数为: "<<Num_of_1(0x7FFFFFFF)<<endl;
	//负数:n为0x80000000
	cout<<"二进制中1的个数为: "<<Num_of_1(0x80000000)<<endl;
	//负数:n为0xFFFFFFFF
	cout<<"二进制中1的个数为: "<<Num_of_1(0xFFFFFFFF)<<endl;
	//特殊测试:n为0
	cout<<"二进制中1的个数为: "<<Num_of_1(0)<<endl;

	return 0;
}
扩展一:用一条语句判断一个整数是不是2的整数次方,一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其他所有位都是0.再由前面所述得到:把这个整数减去1之后再和它自己做与运算,这个整数中唯一的1就会变成0.

扩展二:输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n。比如10的二进制表示为1010,13的二进制表示为1101,需要改变1010中的三位才能得到1101.我们可以分两步去解决它:第一步求这两个数的异或,第二步统计异或结果中1的位数。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值