目录
1. 基础位运算
位运算符是在二进制位级别上对数据进行操作的运算符。以下是一些常见的位运算符:
1. 右移运算符 (>>)
将一个数的所有二进制位向右移动指定的位数。右移运算符 >> 表示将运算符左边的操作数的所有位向右移动右边指定的位数,右边多余的位将被丢弃,左边用符号位填充。
int x = 8; // 二进制: 0000 1000
int result = x >> 2; // 二进制: 0000 0010,即十进制的 2
2. 左移运算符 (<<)
将一个数的所有二进制位向左移动指定的位数。左移运算符 << 表示将运算符左边的操作数的所有位向左移动右边指定的位数,左边多余的位将被丢弃,右边用零填充。
int x = 8; // 二进制: 0000 1000
int result = x << 2; // 二进制: 0010 0000,即十进制的 32
3. 按位取反运算符 (~)
对一个数的每个二进制位执行取反操作,即将 0 变为 1,将 1 变为 0。
int x = 5; // 二进制: 0000 0101
int result = ~x; // 二进制: 1111 1010,即十进制的 -6
4. 按位或运算符 (|)
对两个数的每个二进制位执行按位或操作,只要有一个为 1,结果位就为 1。
int x = 5; // 二进制: 0000 0101
int y = 3; // 二进制: 0000 0011
int result = x | y; // 二进制: 0000 0111,即十进制的 7
5. 按位与运算符 (&)
对两个数的每个二进制位执行按位与操作,只有两个都为 1,结果位才为 1。
int x = 5; // 二进制: 0000 0101
int y = 3; // 二进制: 0000 0011
int result = x & y; // 二进制: 0000 0001,即十进制的 1
6. 按位异或运算符 (^)
对两个数的每个二进制位执行按位异或操作,如果两个位不同则结果位为 1,相同则为 0。或者可以简单记忆为无进位相加。
int x = 5; // 二进制: 0000 0101
int y = 3; // 二进制: 0000 0011
int result = x ^ y; // 二进制: 0000 0110,即十进制的 6
2. 位图
位图(Bitmap)是一种用于表示二进制数据的数据结构,通常用于表示一组二进制位的状态。它的核心思想是用二进制位的值(0或1)来表示某种状态或信息。它的主要核心操作即以下三步
1. 给定一个数n,确定它的二进制表示中的第x位是0还是1
要想判断某一个位置是0还是1只需要对这个位置&上一个1,如果该位置为1则结果为1,反之则结果为0,在这里我们要想快速判断可以使用
(n >> x) & 1
2. 将一个数n的二进制表示的第x位修改成1
要想修改x位置的值为1,只需要对这个位置|上一个1,其余位置|上0,这样就可以将该位置修改成1,即

在这里我们要想快速修改可以使用
(1 << x) | n
3. 将一个数n的二进制表示的第x位修改成0
要想修改x位置的值为0,只需要对这个位置&上一个0,其余位置&上1,这样就可以将该位置修改成0,即

在这里我们要想快速修改可以使用
(~(1 << x)) & n
4. 位图的思想
从使用上来说,位图其实与哈希表相似只不过哈希表是创建一个数组来存储信息,而位图则是使用比特位中的0与1来存储信息。
3. 提取与删除二进制中最右侧的1
1. 提取(lowbit)
要想提取最右侧的0,只需要让n & -n,即

可以看到,这个操作的本质就是将最右侧的1的左边区域全部变为相反的数
2. 删除
要想提取最右侧的0,只需要让n & (n - 1),即

可以看到,这个操作的本质是将最右侧的1的右边区域(包含1)全部变为相反数
4. 异或运算(^)的运算律
异或运算律如下
1. a ^ 0 = a
2. a ^ a = 0
3. a ^ (b ^ c) = (a ^ b) ^ c
5. 应用实例
1. 位1的个数
题目链接:191. 位1的个数 - 力扣(LeetCode)

解析:根据题意我们可以使用删除最右侧的1操作来统计个数,代码如下
class Solution
{
public:
int hammingWeight(uint32_t n)
{
int num = 0;
while (n)
{
num++;
n &= (n - 1);
}
return num;
}
};
2. 比特位计数
题目链接:338. 比特位计数 - 力扣(LeetCode)

解析:这道题也可以使用删除最右侧的1策略来统计,代码如下
class Solution
{
public:
vector<int> countBits(int n)
{
vector<int> ret;
for (int i = 0; i <= n; i++)
{
int num = 0, cur = i;
while (cur)
{
num++;
cur &= (cur - 1);
}
ret.push_back(num);
}
return ret;
}
};
3. 汉明距离

解析:这里我们需要求得不同的二进制位个数,由于异或操作可以达到相异为1,相同为0的效果,我们只需要将两个数异或并统计异或后1的数量即可,代码如下
class Solution
{
public:
int hammingDistance(int x, int y)
{
int ret = 0;
int tmp = x ^ y;
while (tmp)
{
ret++;
tmp &= (tmp - 1);
}
return ret;
}
};
4. 只出现一次的数字
题目链接:136. 只出现一次的数字 - 力扣(LeetCode)

解析:这道题我们可以利用异或的性质,由于只有一个数出现次数为1次,其余数出现次数均为2次,我们只需要将他们全部异或起来就能得到结果,代码如下
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
int res = 0;
for (auto e : nums) res ^= e;
return res;
}
};
5. 只出现一次的数字Ⅲ
题目链接:260. 只出现一次的数字 III - 力扣(LeetCode)

解析:这道题中,有两个数字只出现一次(这里以3(011)和5(101)举例),那么将所有的数异或起来之后,会得到这两个数的异或结果(6(110)),由于异或的性质是相异为1,所以我们可以提取最右边的1(这个1代表3和5在这个位置不同),以它作为标志将整个数组分为2组,再次分别将所有数据异或便可以得到结果,代码如下
class Solution
{
public:
vector<int> singleNumber(vector<int>& nums)
{
int tmp = 0;
for (int e : nums) tmp ^= e;
// 取得最后一位1
// 当tmp为INT_MIN时,即负数的最高位为 1,直接对其取反会导致溢出,需要特殊判断
int flag = tmp == INT_MIN ? tmp : tmp & (-tmp);
int e1 = 0, e2 = 0;
for (int e : nums)
{
if (flag & e) e1 ^= e;
else e2 ^= e;
}
return { e1, e2 };
}
};
6. 判定字符是否唯一
题目链接:面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)

解析:对于这道题我们常规解决思路是创建一个哈希表,并统计每个字符出现次数,在这里我们可以使用位图的思想来快速判断,由于这里s仅包含小写字符,我们可以使用一个int的比特位来确定每个字符的状态,为0则表示没有出现过,反之。此外,我们还可以使用鸽巢原理来对其进行优化
鸽巢原理(Pigeonhole Principle)是一种基本的数学原理,它描述了在一些有限资源分配到一些有限位置时,至少会有一个位置有多个资源的情况。具体来说,鸽巢原理可用于解决问题,例如如果有11个抽屉,放置12只鸽子,那么必定有一个抽屉里至少有两只鸽子。这是因为鸽巢原理指出,将12只鸽子放入11个抽屉中,必然导致至少有一个抽屉里有两只鸽子。
即如果字符串s长度超过26,则表明它一定有一个重复字符,代码如下
class Solution
{
public:
bool isUnique(string astr)
{
// 使用鸽巢原理进行优化
if (astr.size() > 26) return false;
int bitmap = 0;
for (char ch : astr)
{
int i = ch - 'a';
// 若ch位置为1,则表示出现过
if (((bitmap >> i) & 1) == 1) return false;
// 未出现就加入位图
bitmap |= 1 << i;
}
return true;
}
};
7. 丢失的数字
题目链接:268. 丢失的数字 - 力扣(LeetCode)

解析:对于这道题我们可以采取常规的解决思路
解法一:哈希表
我们可以创建一个大小为n+1的哈希表,遍历一遍数组并将对应位置++,到最后查看hash表中value为0的数字,代码如下
class Solution
{
public:
int missingNumber(vector<int>& nums)
{
int ret = 0, n = nums.size();
unordered_map<int, int> hash;
for (auto e : nums) hash[e]++;
for (int i = 0; i <= n; i++)
if (hash[i] == 0) ret = i;
return ret;
}
};
解法二:高斯算法求和
由题意我们可以知道,在所有数字中只有一个数字是缺失的,因此我们只需要计算出总和再减去nums数组的总和即可,代码如下
class Solution
{
public:
int missingNumber(vector<int>& nums)
{
int sum = 0, n = nums.size();
for (auto e : nums) sum += e;
int s = (0 + n) * (n + 1) / 2;
return s - sum;
}
};
解法三:异或运算
除了上面两种解法外,我们还可以使用异或运算将0~n的所有数与nums中的所有数异或,这样得到的最终结果就是缺失的数字,代码如下
class Solution
{
public:
int missingNumber(vector<int>& nums)
{
int res = 0;
for (int i = 0; i <= nums.size(); i++)
{
res ^= i;
if (i < nums.size()) res ^= nums[i];
}
return res;
}
};
8. 两整数之和
题目链接:371. 两整数之和 - 力扣(LeetCode)

解析:(如果这道题出现在笔试中,我们可以直接return a + b;)×,要想不使用+运算符得到两数之和,我们可以使用异或运算符(^)来做到无进位相加,与运算符(&)来做到统计进位,即

代码如下
class Solution
{
public:
int getSum(int a, int b)
{
while (b)
{
int t_a = a, t_b = b;
a ^= b;
b = (t_a & t_b) << 1;
}
return a;
}
};
9. 只出现一次的数字Ⅱ
题目链接:137. 只出现一次的数字 II - 力扣(LeetCode)

解析:读一下题我们可以知道,每一个元素在nums中出现次数都为三次或一次,那么遍历一遍数组后对于每个比特位来说,出现的情况只可能有4种:0, 1, 3n+0, 3n+1(n为出现三次元素的个数),仔细观察可以发现,我们将出现的所有情况%3(3为每个元素恰巧出现的次数),得到的结果就是出现一次的那个元素的正确比特位,我们可以得到代码如下
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
int ret = 0;
for (int i=0; i<32; i++)
{
long long sum = 0;
for(auto e : nums)
if (((e >> i) & 1) == 1) sum++;
sum %= 3;
if (sum) ret |= (1 << i);
}
return ret;
}
};
10. 消失的两个数字
题目链接:面试题 17.19. 消失的两个数字 - 力扣(LeetCode)

解析:要想解决这道题,我们可以将5题与7题相结合,即先将1~N的数字还有nums的数据都异或起来,这样得到的就是两个缺失的数字异或的结果,这之后我们需要创建一个flag来记录异或结果最右边的1(表示两个缺失的数在这个比特位不同),这之后再次遍历0~N与nums,利用flag将数据分散到两组内,最终找到两个数,代码如下
class Solution
{
public:
vector<int> missingTwo(vector<int>& nums)
{
int res = 0, n = nums.size();
for (int i = 1; i <= n+2; i++)
{
res ^= i;
if (i <= n) res ^= nums[i-1];
}
int flag = res & -res;
int num1 = 0, num2 = 0;
for (int i = 1; i <= n+2; i++)
{
if (i & flag) num1 ^= i;
else num2 ^= i;
if (i <= n)
{
if (nums[i-1] & flag) num1 ^= nums[i-1];
else num2 ^= nums[i-1];
}
}
return {num1, num2};
}
};
本文详细介绍了基础位运算符如右移、左移、按位取反、按位或、按位与和按位异或,以及位图的概念、核心操作和在编程问题中的应用实例,包括计数、查找、汉明距离计算等。
1382

被折叠的 条评论
为什么被折叠?



