1. 程式人生 > 其它 >演算法之異或運算及其應用

演算法之異或運算及其應用

異或演算法又可稱為無進位加法,這是一種位運算,位運算速度要比算術運算快得多...

演算法之異或運算及其應用

基本介紹

異或演算法又可稱為無進位加法

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技術小棧