刚开始刷力扣,发现位运算不太熟悉,写一个自看的位运算教程
1.与运算(&)
两个都为1结果才为1,其他为0
1001
& 1010
——————————
1000
2.或运算(|)
两个只要有1,结果就为1,两个都为0,结果才为0
1001
& 1010
——————————
1011
3.异或运算(^)
两个相同为0,不同为1
1001
^ 1010
——————————
0011
4.取反(~)
对应位取反
~ 1001
——————————
0110
5.左移(<<)
高位丢弃,低位补0,相当于乘2
x << 16
移位前:1100 0101 1111 0000 1010 0011 0110 1000
移位后:1010 0011 0110 1000 0000 0000 0000 0000
6.右移(>>)和无符号右移(>>>)
如果高位为1,则补1,高位为0,则补0,相当于除2
x >> 16
移位前1100 0101 1111 0000 1010 0011 0110 1000
移位后1111 1111 1111 1111 1100 0101 1111 0000
奇淫技巧
1.交换a和b的值
void swap(a,b)
{
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
2.位操作(&)来判断最后一位是1还是0,可用于判断奇偶
n = 1101
(n & 1) = 1
3.位操作(&)来判断第一位是1还是0
(n >> 31) & 1
右移到只剩下高位,再 &1,即可判断正负
4.交换符号,正数变负数,负数变正数
取反加1
(~n) + 1
5.将数取绝对值,整数不用变,负数需要取反加1,用上面的知识,先判断正负
int a = n >> 31;
if (0 == a)
return n;
else
return (~n)+1;
6.将高低位进行交换,这是在力扣习题上用的手法(无符号数)
n = 1100 0101 1111 0000 1010 0011 0110 1000
change = (n >>> 16) | (n << 16)
//这里很好理解,将高位无符号右移到低位,低位左移到高位,然后进行或运算
7.统计二进制中1的个数
如果用不断的右移来统计1的个数效率有点低,用一种高效的手法。
a&=(a-1)
a = 1110 0001 0001 0000
count = 0
进行a &= (a-1)
第一次:
1110 0001 0001 0000
& 1110 0001 0000 1111
————————————————————————
1110 0001 0000 0000
count = 1
第二次:
1110 0001 0000 0000
& 1110 0000 1111 1111
————————————————————————
1110 0000 0000 0000
count = 2
第三次:
1110 0000 0000 0000
& 1101 1111 1111 1111
————————————————————————
1100 0000 0000 0000
count = 3
第四次:
1100 0000 0000 0000
& 1011 1111 1111 1111
————————————————————————
1000 0000 0000 0000
count = 4
1000 0000 0000 0000
& 0111 1111 1111 1111
————————————————————————
0000 0000 0000 0000
count = 5
所以可以看到每次减1会将最后一个位1消掉,再和原数据相与变成0,有多少次运算,就有多少个1.代码如下
int count = 0;
while(a)
{
a &= (a-1);
count++;
}
这里给出力扣的题以及题解
颠倒给定的 32 位无符号整数的二进制位。
示例 1:
输入: 00000010100101000001111010011100
输出: 00111001011110000010100101000000
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入:11111111111111111111111111111101
输出:10111111111111111111111111111111
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
第一个方法就是遍历,每次将最右边的位移到左边,可以想到开始时是将最右边的位向左移31位,然后加上后面右边第二位向右移30位,依次类推。
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
int power = 31;
int temp = 0;
while(n)
{
temp += (n&1) << power;
n = n>> 1;
power--;
}
return temp;
}
};
有一种记忆化技术细节暂时没弄懂,先掠过
第三种就是分治,确实巧妙
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
n = (n >> 16) | (n << 16);
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);
return n;
}
};
先将左16位和右16位交换,然后用&运算每一步分治,这里的细节让人一惊,真的强。