1. 程式人生 > 其它 >劍指offer(專項突擊版)

劍指offer(專項突擊版)

劍指 Offer II 001. 整數除法

給定兩個整數 a 和 b ,求它們的除法的商 a/b ,要求不得使用乘號 '*'、除號 '/' 以及求餘符號 '%' 。

注意:

整數除法的結果應當截去(truncate)其小數部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
假設我們的環境只能儲存 32 位有符號整數,其數值範圍是 [−231, 231−1]。本題中,如果除法結果溢位,則返回 231 − 1

  1. 不可以使用除法和乘法,則可以使用加法或位運算。

  2. 因為限制了只能使用23位有符號整數,所以不能使用long。數的範圍為\(-2^{31} 到 2^{31}-1\)

  3. 先考慮特殊情況:

    1. 當a為\(-2^{31}\),b為-1,則結果存在int32時的溢位情況,此時應返回\(2^{13}-1\)
    2. 當a為0或b為1,則直接返回a
    3. 剩餘就是正常情況處理了;正常處理的時候,我們往往會先將符號位確定出來,然後全部當做正數去處理。這時候,存在一個陷阱:a為\(-2^{31}\)且b不為-1時,對a取絕對值,在java中存在溢位情況。那麼,反之可以將其全部當做負數處理就不存在溢位情況了。
    public int divide(int a, int b) {
            // 先考慮特殊情況
            // 考慮溢位情況:-2^31 ~ 2^31 -1
            // a=-2^31 b=-1 則出現上溢,返回2^31 -1
            if (a == Integer.MIN_VALUE && b == -1) {
                return Integer.MAX_VALUE;
            }
            // 被除數為0或除數為1
            if (a == 0 || b == 1) {
                return a;
            }
    
            // 考慮符號
            int flag = (a > 0) ^ (b > 0) ? -1 : 1;
            // 正常處理,有一個陷阱,對-2^31取絕對值存在溢位
            // 所以,反過來求值
            if(a>0) {a=-a;}
            if(b>0) {b=-b;}
            int res = 0;
            while (a<=b){
                a-=b;
                res++;
            }
            return flag*res;
        }
    

    顯然,這樣是最簡單的方法,還有優化的空間。那就是採用位運算。如下所示可以加速上邊程式碼的while過程:

    a=14 b=-2

    Step1:flag = -1

    Step2:a=-14 b=-2

    Step3:

    ​ res = 0

    ​ round1:

    ​ base=1;dividor=-2;dividor=dividor<<2=-8; base=base<<2=4; a=a-dividor=-6; res=res+4=4;

    ​ round2:

    ​ base=1;dividor=-2;dividor=dividor<<1=-4; base=base<<1=2; a=a-dividor=-2; res=res+2=6;

    ​ round3:

    ​ base=1;dividor=-2; (此時while內部迴圈不執行,base為1) a=a-dividor=0; res=res+base=7;

// 顯然divide有更進一步的優化方法
    public int divide1(int a, int b) {
        // 先考慮特殊情況
        // 考慮溢位情況:-2^31 ~ 2^31 -1
        // a=-2^31 b=-1 則出現上溢,返回2^31 -1
        if (a == Integer.MIN_VALUE && b == -1) {
            return Integer.MAX_VALUE;
        }
        // 被除數為0或除數為1
        if (a == 0 || b == 1) {
            return a;
        }

        // 考慮符號
        int flag = (a > 0) ^ (b > 0) ? -1 : 1;
        // 正常處理,有一個陷阱,對-2^31取絕對值存在溢位
        // 所以,反過來求值
        if(a>0) {a=-a;}
        if(b>0) {b=-b;}
        int res = 0;
        // 這裡可以使用位運算加速
        while (a<=b){
            int base = 1;
            int dividor = b;
            while (a-dividor<=dividor){
                dividor<<=1;
                base<<=1;
            }
            a-=dividor;
            res+=base;
        }
        return flag*res;
    }

劍指 Offer II 002. 二進位制加法

給定兩個 01 字串 ab ,請計算它們的和,並以二進位制字串的形式輸出。

輸入為 非空 字串且只包含數字 10

輸入: a = "1010", b = "1011"
輸出: "10101”

  • 每個字串僅由字元 '0''1' 組成。
  • 1 <= a.length, b.length <= 10^4
  • 字串如果不是 "0" ,就都不含前導零。

因為字串的長度足夠大,所以必須採用字串解決,而不能將其轉為10進位制數字後求和再轉為2進位制處理。

思路1:簡單模擬。逆序同步掃描字元,進行累加,後分別對2求餘數的商來更新結果。如下,時間複雜度\(O(n)\),空間複雜度\(O(1)\)。其中\(n=max(a.length(),b.length())\)

public String addBinary(String a, String b) {
        int carry = 0;
        int n = Math.max(a.length(), b.length());
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < n; i++) {
            // 定位導數第i個元素,並累加
            carry += i < a.length() ? (a.charAt(a.length() - i - 1) - '0') : 0;
            carry += i < b.length() ? (b.charAt(b.length() - i - 1) - '0') : 0;
            stringBuffer.append((char) (carry % 2 + '0'));
            carry /= 2;
        }
        if(carry>0){
            stringBuffer.append('1');
        }
        stringBuffer.reverse();
        return stringBuffer.toString();
    }

劍指 Offer II 003. 前 n 個數字二進位制中 1 的個數

給定一個非負整數 n ,請計算 0n 之間的每個數字的二進位制表示中 1 的個數,並輸出一個數組。

輸入: n = 2
輸出: [0,1,1]
解釋:
0 --> 0
1 --> 1
2 --> 10

思路1:分別對每一個數字進行位統計。2進位制位1的個數,可以採用Brian Kernighan演算法,其統計的時間複雜度為\(O(logn)\). 該演算法是這樣的:

令: \(x=x\&(x-1)\) ,該操作可以將x的二進位制位的最後一個1置為0,當x為0的時候,x的二進位制位中就沒有1了。

該思路整體的時間複雜度為\(O(nlogn)\)

public int[] countBits(int n) {
    int[] res = new int[n+1];
    for (int i = 0; i <= n; i++) {
        res[i] = getBits1(i);
    }
    return res;
}
private int getBits1(int i) {
    int count = 0;
    while (i>0){
        i = i&(i-1);
        count++;
    }
    return count;
}

思路2:動態規劃;dp[i]表示i的二進位制中1的個數,則有\(dp[i]=dp[i\&(i-1)]+1\)。該方法時間複雜度為\(O(n)\)。初識dp[0]=0;

public int[] countBits(int n) {
    int[] res = new int[n+1];
    for (int i = 1; i <= n; i++) {
        res[i] = res[i&(i-1)]+1;
    }
    return res;
}