题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1,所以若是输入9,该函数输出2.面试
一、可能引发死循环的解法算法
这是一道很基本的考察二进制和位运算的面试题。题目不是很难,面试官 提出问题以后,咱们很快造成一个基本的思路:先判断证书二进制表示中最右边一位是否是1.接着把输入的证书右移一位,此时原来处于从右边树起的第二位被移 到最后一位,再判断是否是1.这样没移动一位,知道整个整数变成0为止。如今的问题变成怎么判断一个整数的最右边是否是1了。这很简单,只要把整数和1作 位与运算看结果是否是0就知道了。1除了最右边的一位以外全部的位都是0.基于这个思路,咱们很快写出这样的代码:编程
package cglib;函数
public class List1
{
public static int numberOf1(int num) {
int count = 0;spa
while(num!=0){code
if((num&1)!=0)数学
count++;class
num = num>>1;效率
}扩展
return count;
}
public static void main(String[] args) {
System.out.println(numberOf1(9));
System.out.println(numberOf1(-9));
}
}
只输出了2
负数死循环
面试官看了 代码后可能会问:把证书右移一位和把整数除以2在数学上是等价的,那上面的代码中能够把右移换成除以2吗?答案是否认的。由于除法的效率比移位运算要低不少,在实际编程中应尽量地用移位运算代替乘除法。
面试官会问第二个问题就是:上面的函数若是输入一个负数,好比 0x80000000,运行的时候会发生什么状况呢?把负数0x80000000右移一位的时候,并非简单地把最高位的1移到第二位变成 0x40000000,而是0xC0000000.这是由于移位前是个负数,仍然保证移位是个负数,所以移位后的最高位会设为1.若是一直作右移位运算, 最终这个数字会编程0xFFFFFFFF而陷入死循环。
二、常规解法:
为了不死循环,咱们能够不右移输入的数字n.首先把n和1作与运算,判断n的最低位是否是1.接着把1左移一位获得2,再和n作与运算,就能判断n的次低位是否是1。。。。这样反复左移,每次都能判断n的其中一位是否是1.基于这种思路,咱们能够写出这样的代码:
package cglib;
public class List1
{
public static int numberOf1(int num) {
int count = 0;
int flag= 1;
while(flag!=0){
if((num&flag)!=0)
count++;
flag =flag <<1;
}
return count;
}
public static void main(String[] args) {
System.out.println(numberOf1(9));
System.out.println(numberOf1(-9));
}
}
输出:
2
31
这个解法中循环的次数等于二进制中的位数,32位的整数须要循环32次,下面咱们再介绍一个算法,整数中有几个1就只循环几回。
三、能给面试官带来惊喜的算法。
咱们的分析就是:把一个整数减去1,再和原整数作与运算,会把该整数最右边的一个1变成0.那么一个整数的二进制表示中有多少个1,就能够进行多少次运算。基于这种思路,咱们能够写出这样的代码:
package cglib;
public class List1
{
public static int numberOf1(int num) {
int count = 0;
while (num != 0) {
count++;
System.out.println("与前:num="+num);//1001
num = num & (num - 1);//00001001&00001000=00001000,00001000&0000111=00000000
System.out.println("与后num="+num);
System.out.println("count="+count);
}
return count;
}
public static void main(String[] args) {
System.out.println(numberOf1(9));
System.out.println(numberOf1(-9));
}
}
输出:
与前:num=9
与后num=8
count=1
与前:num=8
与后num=0
count=2
2
与前:num=-9
与后num=-10
count=1
与前:num=-10
与后num=-12
count=2
与前:num=-12
与后num=-16
count=3
与前:num=-16
与后num=-32
count=4
与前:num=-32
与后num=-64
count=5
与前:num=-64
与后num=-128
count=6
与前:num=-128
与后num=-256
count=7
与前:num=-256
与后num=-512
count=8
与前:num=-512
与后num=-1024
count=9
与前:num=-1024
与后num=-2048
count=10
与前:num=-2048
与后num=-4096
count=11
与前:num=-4096
与后num=-8192
count=12
与前:num=-8192
与后num=-16384
count=13
与前:num=-16384
与后num=-32768
count=14
与前:num=-32768
与后num=-65536
count=15
与前:num=-65536
与后num=-131072
count=16
与前:num=-131072
与后num=-262144
count=17
与前:num=-262144
与后num=-524288
count=18
与前:num=-524288
与后num=-1048576
count=19
与前:num=-1048576
与后num=-2097152
count=20
与前:num=-2097152
与后num=-4194304
count=21
与前:num=-4194304
与后num=-8388608
count=22
与前:num=-8388608
与后num=-16777216
count=23
与前:num=-16777216
与后num=-33554432
count=24
与前:num=-33554432
与后num=-67108864
count=25
与前:num=-67108864
与后num=-134217728
count=26
与前:num=-134217728
与后num=-268435456
count=27
与前:num=-268435456
与后num=-536870912
count=28
与前:num=-536870912
与后num=-1073741824
count=29
与前:num=-1073741824
与后num=-2147483648
count=30
与前:num=-2147483648
与后num=0
count=31
31
扩展1:用一条语句判断一个整数是否是2的整数次方
将2的幂次方写成二进制形式后,很容易就会发现有一个特色:二进制中只有一个1,而且1后面跟了n个0; 所以问题能够转化为判断1后面是否跟了n个0就能够了。
若是将这个数减去1后会发现,仅有的那个1会变为0,而原来的那n个0会变为1;所以将原来的数与去减去1后的数字进行与运算后会发现为零。
最快速的方法:
(number & number - 1) == 0
扩展2:
输 入两个整数m和n,计算须要改变m的二进制表示中的多少位才能获得n。好比10的二进制表示为1010,13的二进制表示为1101,须要改变1010中 的3位才能获得1101 。 咱们能够分为两步解决这个问题:第一步求这两个数的异或,第二步统计异或结果中1的位数。