这篇博客先介绍了常见位运算操作,然后记录了关于位运算的几道题,包括判定字符是否唯一、丢失的数字、两整数之和、只出现一次的数字2、消失的两个数字。
在这一部分,我们不妨先来总结一下常见位运算操作:
1.基础位运算
>>是右移操作,<<是左移操作,~是按位取反,&是有0就是0,|是有1就是1,^有两种记忆方法1)相同为0,相异为1 2)无进位相加
2.给一个数n,确定它的二进制表示表示中的第x位是0还是1
我们约定,二进制的最右边为第0位,最左边为第31位,操作是(n>>x)&1,如果结果为0,那么第x位就是0,否则为1。
3.将一个数n的二进制表示的第x位修改成1
也就是第x位改为1,其它位保持不变,操作是 n |= (1<<x)。
4.将一个数n的二进制表示的第x位修改成0
操作是 n &= (~(1<<x)) 。
5.位图的思想
上面的2、3、4就是为位图来服务的,位图的本质是哈希表,让一个int的32个位的0/1来记录信息,利用2、3、4可以查看和修改位图。
6.提取一个数(n)二进制表示中最右侧的1(lowbit操作)
先来说结果,操作是 n&-n。我们需要先来看一下-n的本质:
-n的本质是将最右侧的1,左边的区域全部变成相反,此时相&后,左边区域全部变成0,右边区域本来就是0,所以&完之后只剩下最右侧的1。
7.干掉一个数(n)二进制表示中最右侧的1
先来说结果,操作是 n&(n-1) ,我们需要看一下n-1的本质:让最右侧及其右边全变成相反,左侧不变,这样相&之后,左侧不变,右侧全部变成0,这就把最右侧的1干掉了。
8.位运算的优先级
能加括号就加括号。
利用上面这几条,我们就可以解决LeetCode中的191、338、461这三道题了,
9.异或^运算的运算律
1) a^a = 0(消消乐)
2) a^0 = a
3)a^b^c = a^c^b
class Solution {
public:
bool isUnique(string astr)
{
int bitmap = 0;
for(auto e:astr)
{
int pos = e - 'a';
if((bitmap>>pos) & 1) return false;
bitmap |= (1<<pos);
}
return true;
}
};
题目分析:这道题有很多种解法,这里我们学习两种方法:哈希表和位图。第一种,使用哈希表,遍历整个字符串,将字符串中的每个字符依次放到哈希表中并判断条件,这种方法的时间复杂度和空间复杂度都是O(N)。第二种,使用位图,使用一个int位图,0位表示'a',1位表示'b',依次类推,因此只需要用到0~25位,遍历整个字符串,修改对应的bit,bit位为0表示之前没遇到这个字符,bit位为0表示之前遇到过这个字符。
此外,我们还可以使用鸽巢原理(抽屉原理)进行优化,如果字符长度大于26,说明肯定有重复字符,直接返回false。
class Solution
{
public:
int missingNumber(vector<int>& nums)
{
int ret = 0;
for(int i = 0 ; i <= nums.size() ; i++) ret ^= i;
for(auto e : nums) ret ^= e;
return ret;
}
};
题目分析:这道题我们还是有很多思路的,包括哈希表、求和公式、异或运算。第一种,哈希表,将数组中所有数字依次放到哈希表中,然后看哈希表中缺失哪个数字。第二种,求和公式,计算出0~n之和,然后减去数组中的每个元素,剩下的就是缺失的元素。第三种,异或运算,将数组中每一个元素都异或到一起,然后再异或0~n中的每一个元素,结果就是缺失的数字。
class Solution {
public:
int getSum(int a, int b)
{
while(b != 0)
{
int x = a^b;// x是无进位相加的结果
int carry = (a&b)<<1;// carry 是进位
a = x;
b = carry;
}
return a;
}
};
题目分析:这种直接用加法求和,大概率是用位运算操作,我们画图来看一下具体方法,主要想法是,a^b是无进位相加,a&b<<1可以表示进位,当进位为0时,循环结束。
class Solution {
public:
int singleNumber(vector<int>& nums)
{
int i = 0;
int sum = 0;
int ret = 0;
while(i <= 31)
{
//找到所有数第i位相加的结果
for(auto e : nums)
{
sum += (e>>i)&1;
}
if(sum % 3 == 1) ret |= 1<<i;
sum = 0;
i++;
}
return ret;
}
};
题目分析:我们将nums中每个元素的某一位全部加到一起,总共有4中情况:3n个0+0、3n个0+1、3n个1+0、3n个1+1。无论哪种情况,都将加和结果%3,最后得到的0、1、0、1就是所求结果的每一个比特位。
扩展:我们可以把这一道题扩展到除了某个元素只出现一次,其他出现n次,只需在将每一位求和后%n即可。
class Solution {
public:
vector<int> missingTwo(vector<int>& nums)
{
int n = nums.size() + 2;
int tmp = 0;
//1.将所有数字异或到tmp上
for(auto e : nums) tmp ^= e;
for(int i = 1 ; i <= n ; i++) tmp ^= i;
//2.找到a、b中的比特位的那一位
int j = 0;
for(j = 0 ; j < 32 ; j++)
{
if((tmp >> j) & 1) break;
}
//3.按照tmp位分成两组
int a = 0, b = 0;
for(auto e : nums)
{
if((e >> j) & 1) a ^= e;
else b ^= e;
}
//3.根据j位的不同,将所有的数划分为两类来异或
for(int i = 1 ; i <= n ; i++)
{
if((i >> j) & 1) a ^= i;
else b ^= i;
}
return {a,b};
}
};
题目分析:这道题依然是用位运算的方式解决,第一步,先将nums中所有的数异或到同一个变量tmp上,然后再把1~N也异或到这个变量上,最后得到的就是缺失的两个数字的异或。第二步,找到tmp中,比特位为1的那一位。第三步,根据x位的不同,将nums中的数字划分成两组,将这两组分别异或到两个变量上,就可以得到缺失的两个数字。