演算法之異或運算及其應用
演算法之異或運算及其應用
基本介紹
異或演算法又可稱為無進位加法
1 ^ 1 = 0 ( 1 + 1 = 10 ,如果不進位的話,那結果就是0 )
1 ^ 0 = 1 ( 1 + 0 = 1 )
0 ^ 1 = 1 ( 0 + 1 = 1 )
0 ^ 0 = 0 ( 0 + 0 = 0 )
特性
滿足交換律和結合律,表明計算結果和異或順序無關
N ^ 0 = N N ^ N = 0
應用 - 快速交換值
1. 程式碼實現
交換 a 與 b 的值
private void swap(int a, int b) { a = a ^ b; b = a ^ b; a = a ^ b; }
2. 好處
按照上述方式進行值的交換,就無需開闢一個新的空間(不用建立一個變數來輔助進行值的交換)
3. 原理說明
需要使用到的知識為:
異或運算結果和順序無關
N ^ 0 = N N ^ N = 0
說明如下:
譬如 a = 甲,b = 乙
① a = a ^ b => 此時 a = 甲 ^ 乙 , b = 乙
② b = a ^ b => 此時 a = 甲 ^ 乙 , b = 甲 ^ 乙 ^ 乙 = 甲 ^ 0 = 甲
③ a = a ^ b => 此時 a = 甲 ^ 乙 ^ 甲 = 甲 ^ 甲 ^ 乙 = 0 ^ 乙 = 乙 , b = 甲
通過分析,我們可以得到,經過這三步後, a 和 b 的值確實交換了
4. 使用前提
使用這個看似很高逼格的交換前提是 a 和 b 不能指向同一空間 ( 它們的值可以相等,但不能指向同一空間 ), 不然 a 和 b 就都會變成 0 ,因為此時 a ^ b = a ^ a = 0
你沒辦法確保傳進來的兩個值一定是不同的( 在排序演算法及其應用2 - 氣泡排序中我們曾用過這個交換方法,是因為我們確定傳進來的兩個數肯定不是指向同一空間的 ),所以這種抖機靈的寫法是不推薦的,日常最好還是按照我們一般的交換方式來寫
演算法題
1. 問題
有一個數組[]
① 陣列中只有一種數字出現奇數次,其他數字都出現偶數次
② 陣列中有兩種數字出現奇數次,其他數字都出現偶數次
找到出現奇數次的數字
要求: 時間複雜度為 O(n), 空間複雜度為 O(1)
2. 思路
對於第 ① 種情況,所求的奇數次的數字即為陣列中每個元素進行異或後的結果
證明如下:
由異或運算的特性:運算順序不影響異或結果,所以出現偶數次的數字經過異或後就會變成0,所以結果自然只剩下出現奇數次的數字
舉個栗子: 比如 arr = {1, 3, 9, 6, 6, 3, 3, 1, 3} [ 其中唯一出現奇數次的數字為 9 ]
將所有元素異或後得到 1 ^ 3 ^ 9 ^ 6 ^ 6 ^ 3 ^ 3 ^ 1 ^ 3 = 1 ^ 1 ^ 3 ^ 3 ^ 3 ^ 3 ^ 6 ^ 6 ^ 9 = 0 ^ 9 = 9
對於第 ② 種情況,會比第一種難一些,實現思路如下:
首先將陣列中每個元素進行異或,得到的結果就是兩個奇數次的數字的異或,將這個結果賦值給 eor
由於這兩個數字不一樣,所以 eor != 0,這兩個數字的二進位制肯定有一位是不同的
假設它們的第 8 位不同,即得到的異或結果的第 8 位為 1,我們讓 eor' = “第 8 位為 1,其他都為 0”,而陣列中所有元素可以分為兩組, 一組為 ‘第 8 位為 0’,另一組為 ‘第 8 位為 1’,而我們所要求的這兩個數分別在這兩組中( 不可能為同一個組,因為上面已經假設他們第 8 位是不同的,所以必然一個為 1,一個為 0 ) ,並且每一組中除了要求的兩個數,其他數都是偶數次的
我們將陣列元素分別與 eor' 進行 & 運算,將第 8 位上等於 1 (或 0)的數字篩選出來,進行異或運算,這樣就可以得到其中一個出現奇數次的數 a
再將 eor 與 a 進行異或,即可得到另外一個出現奇數次的數 b
聽上去是不是有些抽象,那我們來舉個栗子~
比如 arr = {1, 3, 9, 6, 6, 3, 3, 1, 3, 7} [ 其中唯一出現奇數次的數字為 9 和 7 ]
第一步,先將所有元素進行異或: eor = 1 ^ 3 ^ 9 ^ 6 ^ 6 ^ 3 ^ 3 ^ 1 ^ 3 ^ 7 = 1 ^ 1 ^ 3 ^ 3 ^ 3 ^ 3 ^ 6 ^ 6 ^ 9 ^ 7 = 0 ^ 9 ^ 7= 9 ^ 7 = 14
9 的二進位制 1 0 0 1
7 的二進位制 ^ 1 1 1
-----------------
1 1 1 0 ( 化為十進位制就是14 )
第二步,我們看到 eor 的二進位制結果中至少有一位不為 0,我們選出不為 0 的一位,譬如第 2 位, 那麼 eor' = "第 2 位為1,其他都為 0" = 2
第三步,將 eor' 分別與陣列元素進行 & 運算,就可以將元素分為了兩組
1, 1 | 3, 3, 3, 3
9 | 6, 6
| 7
第 2 位為 0 第 2 位為 1
我們取與 eor' 異或結果為 0 的那一組(你想取結果為 0 或結果為 1 的都可以),即 第 2 位為 0 的那一組,將這組的元素進行異或運算,得到結果 a = 1 ^ 1 ^ 9 = 0 ^ 9 = 9
第四步,再將 a 與 eor 進行異或 ,即可得到結果 b = 9 ^ 14 = 7
a 和 b 即是我們要求的值
3. 程式碼
問題 ① 的程式碼
public int getOneNum(int[] arr) {
int eor = 0;
for (int i : arr) {
eor ^= i;
}
return eor;
}
問題 ② 的程式碼
public void getTwoNum(int[] arr) {
int eor = 0;
for (int i : arr) {
eor ^= i;
}
// eor = 所求兩個數的異或結果
// eor 一定不為0, eor 必然有一位上是 1
int rightOne = eor & (~eor + 1); // 提取出最右位的1
int onlyOne = 0; // eor'
for (int cur: arr){
if ((cur & rightOne) == rightOne) { // 這裡目的是為了分組, == 0 也是可以的
onlyOne ^= cur;
}
}
System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
說明:其中 int rightOne = eor & (~eor + 1); // 提取出最右位的1
用於得到某個數最右邊位置上的1,是一種常規操作,要學會使用
歡迎大家來我部落格逛逛 mmimo技術小棧