1. 程式人生 > >位與進位制

位與進位制

位運算簡介

這裡我假設讀者有二進位制的思維,知道(3)10=(011)2(3)_{10}=(011)_2將十進位制轉換為二進位制的方法

  • &(與)、|(或)、^(異或)、~(非/取反)
  • &gt>和<<運算子將二進位制位進行右移或者左移操作
  • &gt&gt&gt運算子將用0填充高位;>>運算子用符號位填充高位,沒有<<<運算子
  • 對於int型,1<<35與1<<3是相同的,左邊的運算元是int的時需對右側運算元模32,而左邊的運算元是long型時需對右側運算元模64

關於上面的說明,這裡還是補充一下,因為並不是所有人都瞭解位運算

<<的運算規則:按二進位制形式把所有的數字向左移動對應的位數,高位移出(捨棄),低位的空位補零。以3<<2為例,首先把3轉換為二進位制數字0000 0000 0000 0000 0000 0000 0000 0011(int型是4個位元組,1個位元組8位,所以int是32位),然後將上面的二進位制數字向左移動2位後面補0得到0000 0000 0000 0000 0000 0000 0000 1100

在數字沒有溢位的前提下,對於正數和負數,左移一位都相當於乘以2的1次方,左移n位就相當於乘以2n2^n

>>的運算規則:按二進位制形式把所有的數字向右移動對應位數,低為移出(捨棄),高位的空位補符號位,即正數補零,負數補1.以11>>2為例,首先把11轉換為二進位制數字0000 0000 0000 0000 0000 0000 0000 1011,然後把低位的兩個數字移出,因為該數字是正數,所以在高位補零,則得到的最終結果是0000 0000 0000 0000 0000 0000 0000 0010

右移一位相當於除以2,右移n位相當於除以2n2^n

>>>運算規則:和&gt&gt大致相同,區別在於不論是正數還是負數,高位都補零

位運算的奇巧淫技

  • 判斷奇偶位(x & 1 == 1?奇:偶)
  • 獲取二進位制位是1還是0
  • 交換兩個整數變數的值(t = a ^ b;a ^= t;b ^= t;)
  • 不用判斷語句,求整數的絕對值(a ^ (a >> 31)) + (a >>> 31)
  • 結合律 (a^b)^c==a^(b^c)
  • 對於任何數x,都有x^x=0,x^0=x,同自己求異或為0,同0求異或為自己
  • 自反性a^b^b=a^0=a,連續和同一個因子做異或,最終結果為自己

題1:找出唯一成對的數

1-1000這1000個數放在含有1001個元素的陣列中,只有唯一的一個元素重複,其它均只出現一次,每個陣列元素只能訪問一次,設計一個演算法,將它找出來,不用輔助儲存空間

題解

位運算總共就那麼幾條性質,假設一個數組中的值分別是1,2,3,4,2,2是重複的元素,那麼應該怎麼把它挑出來呢,最開始想到的方法肯定是兩重迴圈列舉,時間複雜度是O(n2n^2),不太好,想想怎麼用位運算解決這道題,根據a^a=0,a^0=a這兩條性質,我們可以把陣列中的元素全部異或起來,然後再異或一遍不重複的所有元素,就是(1^2^3^4^2)^(1^2^3^4),這樣把它們湊起來,最後結果就應該是2^2^2=2^0=2,正好是要找的重複元素

程式碼
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		int n = 1001,sum1 = 0,sum2 = 0;
		int[] arr = new int[n];
		for(int i = 0;i < n - 1;i++) {
			arr[i] = i + 1;
			sum1 ^= arr[i];
		}
		arr[n - 1] = new Random().nextInt(n);//隨機數,1-n
		sum2 = sum1 ^ arr[n - 1];
		int idx = new Random().nextInt(n);//隨機選取一個下標
		{//交換
			int t = arr[idx];
			arr[idx] = arr[n - 1];
			arr[n - 1] = t;
		}
		/*for(int i = 0;i < n;i++) {
			System.out.print(arr[i] + " ");
		}*/
		System.out.println(sum1 ^ sum2);
	}
}

題2:找出落單的數

一個數組裡除了某一個數字以外,其他的數字都出現了兩次。請寫程式找出這個只出現一次的數字

題解

這道題比上一道題還簡單,這道題直接將所有的值全部異或起來,得到的結果就是落單的數了

題3:二進位制中1的個數

請實現一個函式,輸入一個整數,輸出該二進位制表示中1的個數

題解

第一種方法:假設這個數是3,其二進位制為011,首先將011&001,判斷得出來的結果是否等於001,如果等於,說明這個第1位是1;然後將011&010,判斷得出來的結果是否等於010,如果等於,說明這個第2位是1,一直進行下去,判斷31位

程式碼
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		int n = cin.nextInt();
		int cnt = 0;
		for(int i = 0;i < 32;i++) 
			if(((1 << i) & n) == (1 << i))
				cnt++;
		System.out.println(cnt);
	}
}
第二種方法

第二種方法:只讓要測驗的數向右移。假設這個數是3,其二進位制為011,首先將011&001,判斷得出來的結果是否等於001,如果等於,說明這個第1位是1;然後將001&001,判斷得出來的結果是否等於001,如果等於,說明這個第2位是1,一直進行下去,判斷31位

程式碼
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		int n = cin.nextInt();
		int cnt = 0;
		for(int i = 0;i < 32;i++) 
			if(((n >>> i) & 1) == 1)
				cnt++;
		System.out.println(cnt);
	}
}
第三種方法

第三種方法:還是假設這個數是3,那我們讓3變成0的過程中肯定是要消掉1的,消掉多少次1,就表示3的二進位制中有多少個1。關鍵就在於,如何消掉011中的1,我們首先讓011減1,那麼011就變成了010,然後讓011&010,就得到了010,然後再讓010減1,那麼010就變成了001,然後讓010&001,就得到了000,轉換成虛擬碼的形式就是,a = (a - 1) & a,它的效果就是消掉最低位上的1,依次消掉所有最低位上的1,最後不久變成了0嗎

程式碼
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		int n = cin.nextInt();
		int cnt = 0;
		while(n != 0) {
			n = (n - 1) & n;
			cnt++;
		}
		System.out.println(cnt);
	}
}

題4:是不是2的整數次方

用一條語句判斷一個整數是不是2的整數次方

題解

這道題比較好想,判斷一個數是不是2的整數次方,其實就是判斷這個數的二進位制數是不是有且僅有一個1,這個和上面那道題很相似,仔細想想,直接給出程式碼了

程式碼
if((n & (n - 1)) == 0)

題5:將整數的奇偶位互換

假設這個數是9,二進位制就是1001,那麼得到的結果就是0110

題解

首先我們需要兩個個數

a = 0x55555555
b = 0xaaaaaaaa

a和b都是16進位制數,轉換為二進位制分別是0101 0101 0101…,1010 1010 1010…(因為1010對應的十進位制是10,而10在16進制中是a,0101也同理),然後將需要改變的數n分別對a和b進行&運算得到c和d,然後將c向左移1位,將d向右移1位,再分別進行異或,就得到所求結果,看下面示例

n = 1001
a = 0101
b = 1010
c = n & a = 0001
d = n & b = 1000
res = (c << 1) ^ (d >> 1) = 0110

題6:0-1間浮點實數的二進位制表示

給定一個介於0和1之間的實數,(如0.625),型別為double,列印他的二進位制表示(0.101),如果該數字無法精確地用32位以內地二進位制表示,則列印“ERROR”

程式碼
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		double num = cin.nextDouble();
		StringBuilder sb = new StringBuilder("0.");
		while(num > 0) {
			double r = num * 2;
			if(r >= 1) {
				num = r - 1;
				sb.append("1");
			} else {
				num = r;
				sb.append("0");
			}
		}
		if(sb.length() > 34) 
			System.out.println("ERROR");
		else
			System.out.println(sb.toString());
	}
}

題7:出現k次和出現1次的數

陣列中只有一個數出現了1次,其他的數都出現了k次,請輸出出現了1次的數

題解

這道題有很多種做法,但是這裡我們只考慮如何利用進位制的方法去做,多的也不說,大家只要記住一個結論k個相同的k進位制數做不進位加法結果為0。舉個例子,3個相同的三進位制數,假設個這個數是2,2的三進位制是011,所以三個011做不進位加法結果就是011+011+011=000;再比方說10個十進位制數相加,假設這個數是10,做不進位加法最後結果也是0

程式碼
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		int[] arr = {3,3,3,5,7,7,7,8,8,8,6,6,6};
		int len = arr.length;
		char[][] kRadix = new char[len][];
		int k = 3;
		int maxlen = 0;
		//轉換成k進位制字元陣列
		//對於每個數字
		for(int i = 0;i < len;i++) {
			kRadix[i] = new StringBuilder(Integer.toString(arr[i],k)).reverse().toString().toCharArray();
			if(maxlen < kRadix[i].length)
				maxlen = kRadix[i].length;
		}
		int[] resArr = new int[maxlen];
		for(int i = 0;i < len;i++) {
			//不進位加法
			for(int j = 0;j < maxlen;j++) {
				if(j > kRadix[i].length) 
					resArr[j] += 0;
				else 
					resArr[j] += (kRadix[i][j] - '0');
			}
		}
		int res = 0;
		for(int i = 0;i < maxlen;i++) 
			res += (resArr[i] % k) * (int)(Math.pow(k,i));
		System.out.println(res);
	}
}