二进制转换
如何将十进制的整数转为二进制?常用的方法是对2不停用竖式取余,直到结果为0,再把得到的一列余数从下到上排列即可。
位运算常用方法
异或(xor)
异或,即两边相同得到0
,不同得到1
,为了方便记忆,可以理解为没有进位的二进制加法。
注意任何数字和0异或都会得到它自身,即a^0=a
。
异或还有一个很重要的性质:a^a=0
,即b^a^a=b
。这个性质会被应用在下面的题目中。
左右移位
顾名思义将一个二进制数向左或向右移动若干位,移出边界的数字被删除,另一端多出的位用0补齐。这里有一种特殊情况,当被右移的数字是一个有符号且为负的整数时,最左端会被1补齐。
当我们需要确定一个数中某个位是0还是1时,可以将1向左移动相应的位数,然后求“与”(&
)。
n & (n - 1)
这个式子的目的是抹掉n
的二进制表示的数的最后一个1(将其变为0)。它的用处非常广,比如可以很方便的找出一个数字的二进制数中有多少个1。
例题
1.数组中只出现一次的数字
题目描述:一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请找出这两个只出现一次的数字。
这道题考察对异或的应用,首先我们考虑比较简单的情况:如果一个数组中除了一个数字之外,其他数字都出现了偶数次,如何找到这个只出现一次的数字。 我们利用异或的性质,如果将这个数组中的所有数字逐一异或,那么最后我们得到的结果就是这个只出现了一次的数字,因为出现偶数次的数字全都异或成了0。
现在再来看有两个只出现一次的数字的情况,类似的,我们可以利用异或的性质,如果将这个数组分为两组,这两个数字刚好出现在这两个组中,问题不就回到了上面讨论的简单情况了吗?因此我们需要一个将两个数字区分开的标准,这个也可以利用异或来实现,如果将数组中的所有元素都一一异或,得到的结果将会是这两个只出现一次数字的异或结果,而我们只需要取其中任意一位进行判断,就可以区分这两个数。比如这里取异或结果的最右边的1,如果数组中的数相应位置是1则分到一个组,是0则分到另一个组,这样两个出现一次的数必定会分在不同的组中,同时也保证了组中的其他数字都出现了偶数次,因为只要是相同的数字,所有位都必定相同。
以下是实现代码:
void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
if(data.size() < 2)
return;
int temp = 0;
for(const int& n : data)
temp ^= n;
*num1 = 0;
*num2 = 0;
int b = 1;
while(1){
if(b & temp)
break;
else
b = b << 1;
}
for(int i = 0; i < data.size(); i++){
if(data[i] & b){
*num1 ^= data[i];
}
else
*num2 ^= data[i];
}
}
2.按位反转一个32bit的整数
uint32_t reverseBits(uint32_t n) {
uint32_t res = n;
res = (res >> 16) | (res << 16);
res = ((res & 0xff00ff00) >> 8) | ((res & 0x00ff00ff) << 8);
res = ((res & 0xf0f0f0f0) >> 4)| ((res & 0x0f0f0f0f) << 4);
res = ((res & 0xcccccccc) >> 2) | ((res & 0x33333333) << 2);
res = ((res & 0xaaaaaaaa) >> 1) | ((res & 0x55555555) << 1);
return res;
}
或者还有另一种思路更容易理解,从右到左依次取输入整数的二进制位,再加到左移了一位的res
中,直到所有位都取到。
uint32_t reverseBits(uint32_t n) {
uint32_t result= 0;
for(int i=0; i<32; i++)
result = (result<<1) + (n>>i &1);
return result;
}