private static void swap(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
複製代碼
先來解釋b 爲何會變成 a。算法
首先,異或運算是符合交換律和結合律的,就是說 a ^ b ^ c 等於 a ^ c ^ b 等於 (a ^ b) ^ c。編程
因此,第二條語句就能夠等價爲 : b = a ^ b ^ b; ,由於(b ^ b) = 0 ,因此 b = a ^ 0 = a。數組
再來解釋 a 爲何會變成 b。ui
結合以上說法,當運行到 第三行代碼時,a = a ^ b 而 b = a,因此 a = a ^ b = a ^ b ^ a。spa
同上,結合異或運算的交換律, a = (a ^ a) ^ b = b。code
其實,只要是互逆運算就能夠進行這種不用臨時變量的交換運算,如 加減法,乘除法。教程
public static void swap(int a,int b) {
a = a + b;
b = a - b;
a = a - b;
}
複製代碼
public static void swap(int a,int b) {
a = a * b;
b = a / b;
a = a / b;
}
複製代碼
public static int plus(int a, int b) {
while (b != 0)
{
int _a = a ^ b;
int _b = (a & b) << 1;
a = _a;
b = _b;
}
return a;
}
複製代碼
主要利用異或運算來完成,異或運算有一個別名叫作:不進位加法。ip
那麼a ^ b就是a和b相加以後,該進位的地方不進位的結果,相信這一點你們沒有疑問,可是須要注意的是,這個加法是在二進制下完成的加法。get
而後下面考慮哪些地方要進位?it
什麼地方須要進位呢? 天然是a和b裏都是1的地方
a & b就是a和b裏都是1的那些位置,那麼這些位置左邊都應該有一個進位1,a & b << 1 就是進位的數值(a & b的結果全部左移一位)。
那麼咱們把不進位的結果和進位的結果加起來,就是實際中a + b的和。
a + b = (a ^ b) + (a & b << 1)
令:
a' = a ^ b, b' = (a & b) << 1 => a + b = (a ^ b) + (a & b << 1) = a' + b'
而後反覆迭代,這個過程是在二進制下模擬加法的運算過程,進位不可能一直持續,因此b最終會變爲0,也就是沒有須要進位的了,所以重複作上述操做就能夠 最終求得a + b的值。
N若是是2的冪次,則N知足兩個條件。
N > 0
N的二進制表示中只有一個1, 注意只能有1個。
由於N的二進制表示中只有一個1,因此使用N & (N - 1)將N惟一的一個1消去,應該返回0。
bool checkPowerOf2(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
複製代碼
x & (x - 1)消去x最後一位的1。
由x & (x - 1)消去x最後一位的1可知。不斷使用 x & (x - 1) 消去x最後一位的1,計算總共消去了多少次便可。
public int countOnes(int num) {
int count = 0;
while (num != 0) // 不能是 > 0,由於要考慮負數
{
num = num & (num - 1);
count++;
}
return count;
}
複製代碼
這個應用是上面一個應用的拓展
思考將整數A轉換爲B,若是A和B在第i(0 <=i < 32)個位上相等,則不須要改變這個BIT位,若是在第i位上不相等,則須要改變這個BIT位。
因此問題轉化爲了A和B有多少個BIT位不相同!
聯想到位運算有一個異或操做,相同爲0,相異爲1,因此問題轉變成了計算A異或B以後這個數中1的個數!
public int countOnes(int num) {
int count = 0;
while (num != 0) // 不能是 > 0,由於要考慮負數
{
num = num & (num - 1);
count++;
}
return count;
}
public int bitSwapRequired(int a, int b) {
return countOnes(a ^ b);
}
複製代碼
思路就是使用一個正整數二進制表示的第i位是1仍是0來表明集合的第i個數取或者不取。 因此從0到2^n-1總共2^n個整數,正好對應集合的2^n個子集。以下是就是 整數 <=> 二進制 <=> 對應集合 之間的轉換關係。
public List<List<Integer>> bitSubsets(int[] nums)
{
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < (1 << n); ++i) {
List<Integer> subset = new ArrayList<>();
for (int j = 0; j < n; j++) {
if((1 << j & i) != 0) {
subset.add(nums[j]);
}
}
list.add(subset);
}
return list;
}
複製代碼
由於只有一個數剛好出現一個,剩下的都出現過兩次,因此只要將全部的數異或起來,就能夠獲得惟一的那個數,由於相同的數出現的兩次,異或兩次等價於沒有任何操做!
public int singleNumber(int[] nums) {
int result = 0, n = nums.length;
for (int i = 0; i < n; i++)
{
result ^= nums[i];
}
return result;
}
複製代碼
由於其餘數是出現三次的,也就是說,對於每個二進制位,若是隻出現一次的數在該二進制位爲1,那麼這個二進制位在所有數字中出現次數沒法被3整除。
對於每一位,咱們讓Two,One表示當前位的狀態。
咱們看Two和One裏面的每一位的定義,對於ith(表示第i位):
若是Two裏面ith是1,則ith當前爲止出現1的次數模3的結果是2
若是One裏面ith是1,則ith目前爲止出現1的次數模3的結果是1
注意Two和One裏面不可能ith同時爲1,由於這樣就是3次,每出現3次咱們就能夠抹去(消去)。那麼最後One裏面存儲的就是每一位模3是1的那些位,綜合起來One也就是最後咱們要的結果。
若是B表示輸入數字的對應位,Two+和One+表示更新後的狀態
那麼新來的一個數B,此時跟原來出現1次的位作一個異或運算,&上~Two的結果(也就是否是出現2次的),那麼剩餘的就是當前狀態是1的結果。
同理Two ^ B (2次加1次是3次,也就是Two裏面ith是1,B裏面ith也是1,那麼ith應該是出現了3次,此時就能夠消去,設置爲0),咱們至關於會消去出現3次的位。
可是Two ^ B也多是ith上Two是0,B的ith是1,這樣Two裏面就混入了模3是1的那些位!!!怎麼辦?咱們得消去這些!咱們只須要保留不是出現模3餘1的那些位ith,而One是剛好保留了那些模3餘1次數的位,`取反不就是否是模3餘1的那些位ith麼?最終對(~One+)取一個&便可。
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for(int i = 0; i < nums.length; i++)
{
ones = (ones ^ nums[i]) & ~twos;
twos = (twos ^ nums[i]) & ~ones;
}
return ones;
}
複製代碼
有了第一題的基本的思路,咱們能夠將數組分紅兩個部分,每一個部分裏只有一個元素出現一次,其他元素都出現兩次。那麼使用這種方法就能夠找出這兩個元素了。不妨假設出現一個的兩個元素是x,y,那麼最終全部的元素異或的結果就是等價於++res = x^y++。
++而且res!=0++
爲何呢? 若是res 等於0,則說明x和y相等了!!!!
由於res不等於0,那麼咱們能夠必定能夠找出res二進制表示中的某一位是1。
對於x和y,必定是其中一個這一位是1,另外一個這一位不是1!!!細細琢磨, 由於若是都是0或者都是1,怎麼可能異或出1
對於原來的數組,咱們能夠根據這個位置是否是1就能夠將數組分紅兩個部分。++x,y必定在不一樣的兩個子集中。++
並且對於其餘成對出現的元素,要麼都在x所在的那個集合,要麼在y所在的那個集合。對於這兩個集合咱們分別求出單個出現的x 和 單個出現的y便可。
public int[] singleNumber(int[] nums) {
//用於記錄,區分「兩個」數組
int diff = 0;
for(int i = 0; i < nums.length; i ++)
{
diff ^= nums[i];
}
//取最後一位1
//先介紹一下原碼,反碼和補碼
//原碼,就是其二進制表示(注意,有一位符號位)
//反碼,正數的反碼就是原碼,負數的反碼是符號位不變,其他位取反
//補碼,正數的補碼就是原碼,負數的補碼是反碼+1
//在機器中都是採用補碼形式存
//diff & (-diff)就是取diff的最後一位1的位置
diff &= -diff;
int[] rets = {0, 0};
for(int i = 0; i < nums.length; i ++)
{
//分屬兩個「不一樣」的數組
if ((nums[i] & diff) == 0)
{
rets[0] ^= nums[i];
}
else
{
rets[1] ^= nums[i];
}
}
return rets;
}
複製代碼
位運算的妙用到這就告一段落了,以上內容有一大部分是來自 九章算法位運算入門教程 , 在 LintCode 上還有相應的編程練習,去練習一下會得到更好的效果。