本文我們來講講二進制的經典應用-求二進制的逆序。本文的重點除了算法本身外,還有<<
和>>>
的神奇應用。
leetcode中有道具體的題目-Reverse Bits,題目很簡單,給你一個32位無符號整數,比如43261596(00000010100101000001111010011100),返回964176192(00111001011110000010100101000000)。
常規解法###
因為題目要求中不去除前導0,所以toString()
方法英雄無用武之地了,常規解法只能一位位枚舉。我們從低到高位枚舉n的二進制碼,然后將低位提到高位來表示逆序后的二進制碼。
代碼很簡單:
var reverseBits = function(n) {
var ans = 0;
for(var i = 31; i >= 0; i--) {
ans |= (n & 1) << i;
n >>= 1;
}
return ans >>> 0;
};
前面都很好理解,return ans >>> 0
可能會引起你的注意,這也正是文章開頭所說的>>>
的神奇應用。
假設我們把n=1帶入程序,將n用二進制碼表示:
0000 0000 0000 0000 0000 0000 0000 0001
for循環運行完后,得到ans的二進制碼為:
1000 0000 0000 0000 0000 0000 0000 0000
但是對於JavaScript而言,它的32位二進制碼的第一位總是符號位,所以上面的二進制碼所表示的數是-2147483648
。現在問題來了,在JavaScript如何把一個signed的32位整數轉成一個unsigned的32位整數? 答案就是>>>0
。於是程序也就很好理解了。
var a = -2147483648; // 1000 0000 0000 0000 0000 0000 0000 0000 signed
console.log(a >>> 0); // 2147483648 unsigned
位運算解法###
我們以12345為例(假設16位無符號整數,32位類似):
0011000000111001
將它逆序后得到新的二進制碼:
1001110000001100
轉換為十進制后,為39948。
第一步:每2個為一組,組內高低位交換
00 11 00 00 00 11 01 10
第二步:每4個為一組,組內高低位交換
1100 0000 1100 1001
第三步:每8個為一組,組內高低位交換
00001100 10011100
第四步:每16個為一組,組內高低位交換
10001110000001100
那么如何用程序來表示呢?譬如第一步中,我們如何快速從0011000000111001
轉為00 11 00 00 00 11 01 10
呢?
將奇數位提取出來:
0_1_0_0_0_1_1_0_
空隙中用0填補(這里為了視覺效果,用了字母):
0a1a0a0a0a1a1a0a
將偶數位同樣提取:
_0_1_0_0_0_1_0_1
空隙處用0填補(這里為了視覺效果,用了字母):
a0a1a0a0a0a1a0a1
將奇數位右移一位,再將偶數位左移一位,相加,即得到結果:
00a1a0a0a0a1a1a0
0a1a0a0a0a1a0a10
0011000000110110
取x的奇數位並將偶數位用0填充代碼實現就是 x & 0xAAAA(1010101010101010),取x的偶數位並將奇數位用0填充代碼實現就是 x & 0x5555(0101010101010101)
因此第一步用代碼實現就是:
x = ((x & 0xAAAA) >> 1) | ((x & 0x5555) << 1);
之后的代碼實現也是類似,我們用C++來解Reverse Bits:
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1);
n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4);
n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8);
n = ((n & 0xFFFF0000) >> 16) | ((n & 0x0000FFFF) << 16);
return n;
}
};
高級語言或者說有unsigned int32的語言,這樣也就可以了。但是蛋疼的是JavaScript是沒有的,我們還是可以用>>>
來進行運算,從而達到操作符號位的效果。
完整代碼:
var reverseBits = function(n) {
n = ((n & 0xAAAAAAAA) >>> 1) | ((n & 0x55555555) << 1);
n = ((n & 0xCCCCCCCC) >>> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xF0F0F0F0) >>> 4) | ((n & 0x0F0F0F0F) << 4);
n = ((n & 0xFF00FF00) >>> 8) | ((n & 0x00FF00FF) << 8);
n = ((n & 0xFFFF0000) >>> 16) | ((n & 0x0000FFFF) << 16);
return n >>> 0;
};
效率之高,看圖說話。
最后再次總結下,JavaScript雖然沒有unsigned int32,雖然位運算過程中第一位始終是符號位,但是並不代表JavaScript不能操縱unsigned int32的整數,因為有如下兩大利器能實現unsigned32位整數和signed32位整數之間的轉換:
var a = -2147483648; // 1000 0000 0000 0000 0000 0000 0000 0000 signed
console.log(a >>> 0); // 2147483648 unsigned
var a = 4294967295; // 1111 1111 1111 1111 1111 1111 1111 1111 unsigned
console.log(a << 0); // -1 signed
當然具體情境還要具體分析。