1. 程式人生 > 其它 >位運算常用公式及練習詳解

位運算常用公式及練習詳解

技術標籤:java演算法

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位置為1x | (1 << k)
將x第k位取反x ^ (1 << k)
將x最右邊的1置為0x & (x - 1)
將x最右邊的0置為1x | (x + 1)
獲取x二進位制位的最後一個1x & (~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 = 0x ^ 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));
}

輸出結果如下圖所示

在這裡插入圖片描述


【位運算題目參考資料】《程式設計師程式碼面試指南》左程雲著