題目:請實現一個函數,輸入一個整數,輸出該數二進制表示中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的位數。