
異或(^) 這個位操作運算符相信大家一定都不陌生,這個運算符可以用來解決很多普通算法解決不了的問題,而且位運算是直接對二進制碼做運算,相對普通的加減乘除運算符來說的話更加的高效,我們借着題目一起來看看。
1
對兩個輸入參數做加法運算,但是不能使用 “+” 運算符 。
解法思路
看到這樣的問題,能想到的隻有位運算,問題是怎麼算?相信大家國小剛學習加法的時候,對于一下子不能得到答案的題,肯定會在草稿紙上列豎式,從右向左算,同一列對下來的數字相加如果超過 10,那麼肯定要在下面寫兩個數字相加後的個位數,然後往前進一位,下一位運算時就要加上這個進位,用這樣的方式直到最後算出結果。這題的思路也是一樣的,隻不過有兩點不一樣,第一,10 進制變成了 2 進制,第二,我們不再是在草稿紙上列豎式,而是要寫成計算機看得懂的代碼,這就得借助我們的位運算了,因為 2 進制表示的數中隻會出現 0 和 1,你可以把這兩個數看成是 true 和 false,這樣更好了解,我們可以先通過異或塞選出不用進位的情況,然後再用與運算和左移運算計算出進位的情況,疊代更新出最後的結果。
參考代碼
public int plus(int a, int b) { int aTemp = 0, bTemp = 0; while (b != 0) { aTemp = a ^ b; bTemp = (a & b) << 1; a = aTemp; b = bTemp; } return a; }
02
如何在不建立臨時變量的情況下進行交換兩個數?
異或的常見應用,很簡單,但是注意思考角度從位出發,而不是數,這點很重要。
public void swap(int a, int b) { a ^= b; // a 中存放兩數互異的點位 b ^= a; // 取反 b 中不同于 a 的點位,也就是實作了 b = a a ^= b; // 取反 a 中不同于 b 的點位,也就是實作了 a = b }
03
如果把 A 轉換成 B ,需要改變多少位?
異或的簡單應用,兩個數做異或的結果就是兩個數差異所在,然後隻需計算這個結果中有多少個 1 即可。
public int convertA2B(int A, int B) { int n = A ^ B; int count = 0; while (n != 0) { n = n & (n - 1); // n - 1 是将 n 的最低位為零 count++; } return count; }
04
LeetCode 第 136 号問題:給定一個非空整數數組,除了某個元素隻出現一次以外,其餘每個元素均出現兩次。找出那個隻出現了一次的元素。
異或的三個點順下來,就可以很清楚地解這道題:
異或運算和乘法一樣,位置和運算順序不影響最後結果:
a^b^c = b^c^a
兩個相同的數做異或運算結果為零:
a^a = 0
任何數和零做異或結果還是這個數本身:
a^0 = a
public int findOnce(int[] arr) { int result = 0; for (int i = 0; i < arr.length; ++i) { result ^= arr[i]; } return result; }
05
LeetCode 第 137 号問題:給定一個非空整數數組,除了某個元素隻出現一次以外,其餘每個元素均出現了三次。找出那個隻出現了一次的元素。
這題的難點在于 3 次,如果把數組裡面的數字就當作數字本身來看的話,很難找到突破口;如果想到了位運算,那就要有一個概念就是位運算是基于位的,而不是基于數的,在這個問題中,所有的 bit 的出現次數隻會有兩種情況,
3*n,3*n + 1
,這裡的 n 是任意整數,假設你周遊數組,其實會有一個中間态就是
3*n + 2
存在,對于除這個數以外的其他數,過程大概是
3*n + 1
->
3*n + 2
3*n
,我們隻要記錄的就是
3*n + 1
和
3*n + 2
的情況,因為
3*n
其實就是一個初始狀态(n=0),記不記錄和我們最後要傳回的答案無關,而記錄
3*n + 2
是為了恢複一些 bit 從
3*n + 2
到
3*n
public int singleNumber(int[] nums) { int ones = 0, twos = 0; for (int i = 0; i < nums.length; ++i) { // 找出目前的 3n + 1 ones = (nums[i] ^ ones) & (~twos); // 找出目前的 3n + 2 twos = (nums[i] ^ twos) & (~ones); } return ones; }
06
LeetCode 第 260 号問題:給定一個整數數組
nums
,其中恰好有兩個元素隻出現一次,其餘所有元素均出現兩次。找出隻出現一次的那兩個元素。
這題的主要難點是如何把兩個數給拆出來,如果直接運用異或算法,我們最後得到的結果是兩個數做異或的結果,關鍵點是如何基于這個異或的結果來找到這兩個數,有一點很重要的就是,異或的結果為 1 的點位隻會出現在其中一個數中,我們可以用其中一個為 1 的點位作為判斷依據,這個點位存在的所有數在一起做異或,這個點位不存在的所有數一起做異或,這樣就把這個問題拆解成了兩個 problem 3。
public int[] singleNumber(int[] nums) { if (nums == null || nums.length == 0) { return new int[2]; } int different = 0; for (int i : nums) { different ^= i; } different &= -different; int[] ans = {0, 0}; for (int i : nums) { if ((different & i) == 0) { ans[0] ^= i; } else { ans[1] ^= i; } } return ans; }
總結
位運算相對其他的運算來說更加的高效,異或在位運算中的應用非常廣,但是這裡的難點是我們平時可能會忽視位運算,導緻我們遇到一般的問題不會往位運算的方向去想,另外就是如果對二進制的運算不熟,我們也很難了解一些位運算的綜合操作,這裡提到了異或可以交換兩個數,做加法操作,還可以用來解決一些問題。