1. 程式人生 > >劍指Offer-其他-(1)

劍指Offer-其他-(1)

知識點:位運算和二進位制。

題目描述
輸入一個整數,輸出該數二進位制表示中1的個數。其中負數用補碼錶示。

1:什麼是位運算

位運算是把數字用二進位制表示之後,對每一位上0或1的運算。二進位制及其位運算是現代電腦科學的基石,很多底層的的技術都離不開位運算 。

與:&(有0為0);

或:|(有1為1) ;

異或:^(不同為1) ;

**左移運算子:**m<<n(把m左移n位,最左邊的n位將被丟棄,右邊補上n個0);

**右移運算子:**m>>n(把m右移n位,最右邊n位將被丟棄:如果數字是一個無符號整數,則用0填補最左邊的n位;如果 數字是一個有符號的數值,則用數字的符號位填補最左邊的第n位。也就是說數字原先是一個正數,則右移之後在左邊補n個0,如果數字原先是負數,則右移動之後在左邊補n個1.例如:00001010>>2=00000010 /10001010>>>3 11110001)

2:Java中的整數型別

在Java中,整數資料型別可以分為long,int,short以及byte型別;

long(長整型)為64位,也就是8個位元組(bytes)可以表示的範圍是:-2 ^63 (-9223372036854775808)~2 ^63-1(9223372036854775807);

int為32位,也就是4個位元組(bytes),可以表示的範圍是-2 ^31(-2147483648)~ 2 ^31-1(2147483647);

short(短整型)為16位,也就是兩個位元組(bytes),可以表示的範圍是(-32768~32767);

byte型別(位元組型別),也就是一個位元組(bytes),可以表示的範圍是(-128~127);

3:計算機中的整數的儲存
計算機中帶符號的整數採用二進位制的補碼進行儲存
在計算機中

在這裡插入圖片描述在這裡插入圖片描述

在這裡插入圖片描述

正數:原碼,反碼,補碼都是一致的;

負數:(觀察補碼的轉換,題目中說負數是以補碼的形式存在)

/一個整數按照絕對值大小轉換成的二進位制數,是為原碼。
5的原碼:00000101。
/

反碼是和原碼反著來的。

反碼加一叫補碼。

補碼就是負數在計算機中的二進位制表示方法。那麼,11111011表示8位的-5;

如果要表示16位的-5 ,在左邊添上8個1即可。

4:分析題目《劍指Offer》

基本思路:先判斷整數二進位制表示中最右邊一位是不是1;接著把輸入的整數右移一位,再判斷是不是1;這樣直到整個整數變為0為止。(判斷整數的最右邊是不是1需要把整數和1做位於運算

看結果是不是0)。程式碼如下(執行沒有通過,提示迴圈有錯或者演算法複雜度過大)

public class Solution{
    public int NumberOf1(int n){
        int count=0;
        while(n!=0){
            if((n&1)==1)
            {count++;}
            n=n>>1;
        }
        return count;
    }
}

上面的程式碼有一個問題:如果那個數字是一個負數。如0x80000000右移一位,不是變成了0x40000000,而是變成了0xC0000000(因為高位補1了)。所以移位後的最高位會被設定為1.那麼最終這個數字會變成0xFFFFFFF而陷入死迴圈。

把整數右移一位和把整數除以2在數學上是等價的,但是除法的效率比位運算低得多,在實際的程式設計中應儘可能的使用移位運算代替除法

3:下面是通過的程式碼

針對上述的問題,我們可以補右移數字n。首先把n和1做位於運算,判斷n的最低是不是1.接著把1左移一位得到2,再和n做與運算,,,,,反覆左移;在這個解法中迴圈的次數是32次(相當於整數二進位制的位數)

程式碼如下面

public class Solution{
    public int NumberOf1(int n){
        int count=0;
        int flag=1;
        while(flag!=0){
            if((n&flag)==flag){count++;}
            flag=flag<<1;
        }
        return count;
    }
}

下面是一個驚喜的做法:

1:先分析一個問題(一個整數減去1

如果一個整數不等於0,那麼該整數的二進位制表示至少有一位是1:如果這個數的最右邊是1,那麼減去1之後,最後一位變成0而其他所有位都保持不變,也就是最後一位相當於做了取反操作,由1變成了0;如果最後一位不是1是0,假設該整數二進位制表示中最右邊的1位於第m位,那麼減去1的時候,第m位由1變成了0,第m位之後的數字都變成了1,第m位前面的數字都不變。

從上面的案例啟發:把一個整數減去1,再和原整數進行與運算,會把該整數最右邊的1變成0;那麼一個整數的二進位制表示中有多少個1,就可以進行多少次這樣的操作。基於這種思路,寫出如下程式碼:

public class Solution{
    public int NumberOf1(int n){
        int count=0;
        while(n!=0){
            count++;
                   
                n=n&(n-1);
        }
        return count;
    }
}

觀察下圖:-5的補碼(以byte型別儲存)有
在這裡插入圖片描述

在這裡插入圖片描述

最終除錯程式碼如下:

package 一個整數二進位制數中1的位數;

public class Solution{
    public static int NumberOf1(int n){
        int count=0;
        while(n!=0){
            count++;
                   
                n=n&(n-1);
        }
        return count;
    }
}

下面是測試程式碼:

package 一個整數二進位制數中1的位數;

public class Test {
	public static void main(String[] args) {
		int flag1 = Solution.NumberOf1(5);
		System.out.println(flag1);
		
		int flag2 = Solution.NumberOf1(0);
		System.out.println(flag2);
		
		int flag3 = Solution.NumberOf1(-5);
		System.out.println(flag3);
		
	}
}

下面是執行結果,計算機預設的是32位,也就是long型別的資料。(徹徹底底的搞懂了啊!!!!!!!!不將就!!!!!)
在這裡插入圖片描述