算法之異或運算及其應用


算法之異或運算及其應用

基本介紹

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM