剑指offer—二进制中1的个数
问题分析
本题是为了求解一个整数的二进制中1的个数。比如9(1001),它的二进制数就有两个1。
这类的题目大部分都会用到位运算的知识,位运算非常抽象,而且在日常的生活中很少碰到,但是位运算的核心很简单,因为位运算的种类就那么几个(或、与、非、异或、左移和右移),所以只要将这些基本的操作熟记于心,这类的题目就好做了。
回到上面这题。第一种解法就是不断的将这个数进行右移操做,直到这个数变为零,每次检查最低为是否为1,如果为1,那么1的个数就加1。比如9,右移的操作结果分别为1001 >> 100 >> 10 >> 1 >> 0,只有1001和1末位为1,只有这两个数与1做与运算结果为1,所以结果为2。
细心的同学很快就会发现,如果给的数是一个是负数,那么右移的话每次都会在左边补1,那这样就陷入了一个死循环中,程序出错。
下面是第二个方法,既然题目所给的数在移位的时候可能会是负数,进而出现死循环,那么我们是否可以找一个不会出现死循环的数?在上面的每次移位后都会与1进行与运算,就是用“1”来检测每一位是否是1,既然这样那么可不可以让“1”移动呢?当然可以的,我们可以让1不断的左移,这样就不断的在右边补零。开始将1设为unsigned int,所以右移32位后,就会变成0,这样就可以作为一个循环停止的条件。
第二种解答会执行32次操作,这样来做还是有点多。既然所给的数的二进制中1的个数有限,那么能不能每一次操作都能“剥离”一个1呢?这种解法相比较前面的两个方法要复杂一点,要对位运算具有很好的理解。首先,当给定的数不为0时,说明二进制中含有1,先将1的次数加上1,接下来将给的数减1。减1后有一个效果就是从右往左的第一个1变成0,1右边的0全部变为1,左边的1和0保持不变。接下来就是与运算上场了,这两个数直接相与,那么原来右往左的第一个1以及后面的数全部都变成了0,至此就完成了1的“剥离”(如9的1001和1000相与结果为1000,“剥离”了一个1)!后面就是循环操作相与后结果,直至结果变为0。
源码
// 第二种解法
int numbersOfOneNormalVer(int number){
int count = 0;
unsigned int flag = 1;
while (flag){
if (number & flag)
count += 1;
flag <<= 1;
}
return count;
}
// 第三种解法
int numbersOfOneSpecialVer(int number){
int count = 0;
while (number){
++count;
number &= (number - 1);
}
return count;
}