劍指offer(專項突擊版)
劍指 Offer II 001. 整數除法
給定兩個整數 a 和 b ,求它們的除法的商 a/b ,要求不得使用乘號 '*'、除號 '/' 以及求餘符號 '%' 。
注意:
整數除法的結果應當截去(truncate)其小數部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
假設我們的環境只能儲存 32 位有符號整數,其數值範圍是 [−231, 231−1]。本題中,如果除法結果溢位,則返回 231 − 1
-
不可以使用除法和乘法,則可以使用加法或位運算。
-
因為限制了只能使用23位有符號整數,所以不能使用long。數的範圍為\(-2^{31} 到 2^{31}-1\)
-
先考慮特殊情況:
- 當a為\(-2^{31}\),b為-1,則結果存在int32時的溢位情況,此時應返回\(2^{13}-1\)
- 當a為0或b為1,則直接返回a
- 剩餘就是正常情況處理了;正常處理的時候,我們往往會先將符號位確定出來,然後全部當做正數去處理。這時候,存在一個陷阱: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 字串
a
和b
,請計算它們的和,並以二進位制字串的形式輸出。輸入為 非空 字串且只包含數字
1
和0
。輸入: 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
,請計算0
到n
之間的每個數字的二進位制表示中 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;
}