位與進位制
位運算簡介
這裡我假設讀者有二進位制的思維,知道將十進位制轉換為二進位制的方法
- &(與)、|(或)、^(異或)、~(非/取反)
- >>和<<運算子將二進位制位進行右移或者左移操作
- >>>運算子將用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位就相當於乘以
>>的運算規則:按二進位制形式把所有的數字向右移動對應位數,低為移出(捨棄),高位的空位補符號位,即正數補零,負數補1.以11>>2為例,首先把11轉換為二進位制數字0000 0000 0000 0000 0000 0000 0000 1011,然後把低位的兩個數字移出,因為該數字是正數,所以在高位補零,則得到的最終結果是0000 0000 0000 0000 0000 0000 0000 0010
右移一位相當於除以2,右移n位相當於除以
>>>運算規則:和>>大致相同,區別在於不論是正數還是負數,高位都補零
位運算的奇巧淫技
- 判斷奇偶位(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(),不太好,想想怎麼用位運算解決這道題,根據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);
}
}