位運算常用公式及練習詳解
JAVA位運算子詳解
詳細見知乎穆哥學堂
在java語言中,提供了7種位運算子,分別是按位與(&)、按位或(|)、按位異或(^)、取反(~)、左移(<<)、帶符號右移(>>)和無符號右移(>>>)。這些運算子當中,僅有~是單目運算子,其他運算子均為雙目運算子。位運算子是對long、int、short、byte和char這5種類型的資料進行運算的,我們不能對double、float和boolean進行位運算操作
【注意】
>> 帶符號右移
,右移時左邊空出的位置用符號位上的數字來填充,即正數用0來填充,負數用1來填充
>>> 無符號右移
,右移時左邊空位用0來填充
【補碼小知識】
-
計算機中運算是通過補碼來實現的,補碼可以簡化電路運算
-
正數的補碼等於原碼;負數的補碼等於反碼加1,而反碼等於原碼符號位不變,其餘各位取反
-
在二進位制碼中,採用最高位是符號位的方法來區分正負數,正數的符號位為0、負數的符號位為1。剩下的就是這個數的絕對值部分。通過將負數轉為二進位制原碼,再求其原碼的反碼,最後求得的補碼即負數的二進位制表示結果
例如整數-1的對應表示如下所示
-1 二進位制對應表示 原碼 1000 0000 0000 0000 0000 0000 0000 0001 反碼 1111 1111 1111 1111 1111 1111 1111 1110 補碼 1111 1111 1111 1111 1111 1111 1111 1111
常用公式
對數字x的操作 | 公式 |
---|---|
得到x第k位值 | (x >> k) & 1 |
將x第k位置為1 | x | (1 << k) |
將x第k位取反 | x ^ (1 << k) |
將x最右邊的1置為0 | x & (x - 1) |
將x最右邊的0置為1 | x | (x + 1) |
獲取x二進位制位的最後一個1 | x & (~x) 或 x & - (x - 1) |
判斷奇偶性 | x & 1 |
變為相反數-x | ~(x - 1) 或者 ~x + 1 |
x ^ 0 = x
x ^ x = 0
不用額外變數交換兩個整數的值
【題目】給定整數a和b,交換a和b的值
public void swap(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
整數的二進位制表達中有多少個1
【題目】給定一個32位整數n,可為0,可為正,也可為負,返回該整數二進位制表達中1的個數
【解法一】呼叫java的API
public int count1(int n) {
return Integer.bitCount(n);
}
【解法二】將整數n每次無符號右移一位,檢查最右邊的bit是否為1來進行統計
public int count1(int n) {
int res = 0;
while(n != 0) {
res += n & 1;
n >>>= 1;
}
}
【解法三】使用公式 x & (x - 1)
將n最後一個1置為0
public int count1(int n) {
int res = 0;
while(n != 0) {
res++;
n &= (n - 1);
}
}
【解法四】使用公式 x & (~x)
或 x & - (x - 1)
得到n最後一個1
public int count1(int n) {
int res = 0;
while(n != 0) {
res++;
n -= n & (~n);
}
}
在其他數都出現偶數次的陣列中找到出現奇數次的數
【題目】給定一個整數陣列,其中有一個數出現了奇數次,其它的數都出現了偶數次,返回這個數
【要求】時間複雜度為O(N),額外空間複雜度為O(1)
使用公式 x ^ x = 0
和 x ^ 0 = x
很容易想到···
public int getOddTimesNum(int[] arr) {
int e = 0;
for(int x : arr) {
e ^= x;
}
return e;
}
【進階】有兩個數都出現了奇數次,其它的數都出現了偶數次,返回這兩個數
將陣列元素都進行異或操作後得到的是這兩個數的異或結果c = a ^ b(假如這兩個數為a和b),使用x & (~x)
得到c的最後一個1的位置,由於是異或操作,這最後一個1a和b中必定只有一個含有,將此位置為1的所有數異或後可得到一個結果e,e ^ c為另一個結果
public int[] getOddTimesNum2(int[] arr) {
int e = 0, eo = 0;
for(int x : arr) {
e ^= 0;
}
int rightOne = e & (~e);
for(int x : arr) {
if((x & rightOne) != 0) {
eo ^= x;
}
}
return new int[]{e, e ^ eo};
}
在其他數都出現k次的陣列中找到只出現一次的數
【題目】給定一個整型陣列arr和一個大於1的整數k,已知arr中只有一個數出現了1次,其他的數都出現了k次,請返回只出現1次的數
【要求】時間複雜度為O(N),額外空間複雜度為O(1)
【方法一】統計32位每一位陣列中數字在該位出現的次數,出現次數對k求餘不為0就是隻出現一次的數字在該位置也為1,對相應位置出現的1相與就得到了結果(即相加)
public int singleNumber(int[] nums, int k) {
int res = 0;
int e = 0, count = 0;
for(int i = 0; i < 32; i++) {
e = (1 << i);
count = 0;
for(int x : nums) {
if((x & e) != 0) {
count++;
}
}
if(count % k != 0) {
res |= e;
}
}
return res;
}
【方法二】將數字轉化為k進位制數,對這些數字進行無進位相加(與運算)得到的結果就是每一位對k求餘
……
位運算實現加減乘除
【題目】給定兩個32位整數a和b,可正、可負、可0。不能使用算數運算子,分別實現a和b的加減乘除運算
【要求】如果給定的a和b執行加減乘除的某些結果本來就會導致資料的溢位,那麼你實現的函式不必對那些結果負責
實現加減乘除的相關程式碼如下圖所示
class BitOperationsFour{
BitOperationsFour(){
}
/*** 加法 ***/
public int add(int a, int b) {
int sum = a;
while(b != 0) {
sum = a ^ b; //不考慮進位
b = (a & b) << 1; //只考慮進位
a = sum;
}
return sum;
}
/*** 得到相反數 取反加1(補碼) ***/
public int negNum(int n) {
return add(~n, 1);
}
/*** 減法 ***/
public int minus(int a, int b) {
return add(a, negNum(b));
}
/*** 乘法 ***/
public int multi(int a, int b) {
int res = 0;
while(b != 0) {
if((b & 1) == 1) {
res = add(res, a);
}
a <<= 1;
b >>>= 1; //無符號右移,前面補0
}
return res;
}
/*** 判斷是否為負數 ***/
public boolean isNeg(int n) {
return n < 0;
}
/*** 除法計算 ***/
public int div(int a, int b) {
int x = isNeg(a) ? negNum(a) : a;
int y = isNeg(b) ? negNum(b) : b;
int res = 0;
for(int i = 31; i > -1; i = minus(i, 1)) {
if((x >> i) >= y) {
res |= (1 << i);
x = minus(x, y << i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
/*** 除法分情況 ***/
public int divide(int a, int b) {
if(b == 0) {
throw new RuntimeException("divisor is 0");
}
if(a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
return 1;
}else if(b == Integer.MIN_VALUE) {
return 0;
}else if(a == Integer.MIN_VALUE) {
int res = div(add(a, 1), b);
return add(res, div(minus(a, multi(res, b)), b));
}else {
return div(a, b);
}
}
}
主函式呼叫程式碼如下所示
public static void main(String[] args){
int a = 24, b = -8;
BitOperationsFour op = new BitOperationsFour();
System.out.println("a = " + a + ", b = " + b);
System.out.println("a + b = " + op.add(a, b));
System.out.println("a - b = " + op.minus(a, b));
System.out.println("a * b = " + op.multi(a, b));
System.out.println("a / b = " + op.divide(a, b));
}
輸出結果如下圖所示
【位運算題目參考資料】《程式設計師程式碼面試指南》左程雲著