
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在算法学习的过程中,位运算往往是一个既基础又容易被忽视的知识点.它不像排序、动态规划那样"显眼",却常常在优化空间复杂度、提升运行效率、设计巧妙解法时发挥关键作用.很多看似复杂的问题,一旦转换到二进制视角,就会变得清晰而直接.本文中的相关题目,正是帮助我们深入理解位运算思想的良好切入点.通过这些实战题目,不仅可以掌握与、或、异或、左移、右移等常见操作的基本规律,更重要的是逐步建立一种"从二进制结构出发分析问题"的逻辑思维方式.这样的思维,不只是为了应对面试题,更是在训练我们用更底层、更抽象的方式理解数据与运算本质.本文将结合具体题目,围绕位运算在判奇偶、交换变量、去重查找、状态压缩以及技巧性优化中的应用展开分析.在解题的过程中,我们不仅关注"怎么做",更关注"为什么这样做更高效".希望通过这些内容,能够让位运算不再只是几个符号的机械组合,而成为解决问题时真正可用的一把"利器".废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
1.常见的位运算总结
位运算是直接操作二进制位 的运算,执行效率远高于加减乘除等算术运算,是算法优化、底层开发、状态控制的核心技巧.先明确:位运算仅针对二进制位(0/1) 操作,所有数字都会先转为二进制再计算.
1️⃣按位与 &
规则 :两位都为 1,结果为 1;否则为 0
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
示例 :3 (011) & 5 (101) = 1 (001)
2️⃣按位或 |
规则 :任意一位为 1,结果为 1
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
示例 :3 (011) | 5 (101) = 7 (111)
3️⃣按位异或 ^(最常用!)
规则 :两位不同 为 1,相同 为 0
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
核心特性:
x ^ 0 = x(任何数异或0等于自身)x ^ x = 0(任何数异或自身等于0)- 满足交换律/结合律
示例 :3 (011) ^ 5 (101) = 6 (110)
4️⃣按位取反 ~
规则 :0变1,1变0(一元运算符)
⚠️注意:有符号数中,取反结果为 ~x = -x - 1
示例 :~3 = -4
5️⃣左移 <<
规则 :二进制位左移n位 ,右侧补0
等价 :x << n = x × 2ⁿ
示例 :3 (011) << 1 = 6 (110)
6️⃣右移 >>
规则 :二进制位右移n位 ,左侧补符号位 (正数补0,负数补1)
等价 :x >> n = x ÷ 2ⁿ(向下取整)
示例 :5 (101) >> 1 = 2 (10)

1.1位1的个数(OJ题)

核心代码
cpp
class Solution {
public:
int hammingWeight(int n) {
int count = 0;
//转为无符号数,避免负数符号位干扰
unsigned int num = n;
while (num != 0) {
//清除最低位的1
num &= num - 1;
count++;
}
return count;
}
};
1.2比特位计数(OJ题)

核心代码
cpp
class Solution {
public:
vector<int> countBits(int n) {
vector<int> ans(n + 1);
for (int i = 0; i <= n; ++i) {
int cnt = 0;
unsigned int num = i; //避免符号位干扰
while (num != 0) {
num &= num - 1; //清除最低位的1
cnt++;
}
ans[i] = cnt;
}
return ans;
}
};
1.3汉明距离(OJ题)

核心代码
cpp
class Solution {
public:
int hammingDistance(int x, int y) {
int z = x ^ y;
int cnt = 0;
while (z != 0) {
z &= z - 1; //清除最低位的1
cnt++;
}
return cnt;
}
};
1.4只出现一次数字(OJ题)

核心代码
cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int result = 0;
for (int num : nums) {
result ^= num;
}
return result;
}
};
1.5只出现一次的数字|||(OJ题)

核心代码
cpp
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int xor_sum = 0;
//1. 全员异或,得到两个目标数的异或结果
for (int num : nums) {
xor_sum ^= num;
}
//2. 找到最低位的1,作为分组掩码
// 补码特性:保留最低位1,其余位清0,循环找到最低位的1,无溢出风险
int mask = 1;
while ((xor_sum & mask) == 0) {
mask <<= 1;
}
//3. 按mask分组,分别异或得到结果
int a = 0, b = 0;
for (int num : nums) {
if (num & mask) {
a ^= num; // 该组包含其中一个目标数
} else {
b ^= num; // 该组包含另一个目标数
}
}
return {a, b};
}
};
2.判断字符是否唯一(OJ题)

算法思路:解法(位图的思想)
利用位图的思想,每一个比特位代表一个字符,一个 int 类型的变量的 32 位足够表示所有的小写字母.比特位里面如果是0,表示这个字符没有出现过.比特位里面的值是1,表示该字符出现过.那么我们就可以用一个整数来充当哈希表.
核心代码
cpp
class Solution
{
public:
bool isUnique(string astr)
{
//利⽤鸽巢原理来做的优化
if (astr.size() > 26)
return false;
int bitMap = 0;
for (auto ch : astr)
{
int i = ch - 'a';
//先判断字符是否已经出现过
if (((bitMap >> i) & 1) == 1)
return false;
//把当前字符加⼊到位图中
bitMap |= 1 << i;
}
return true;
}
};
完整测试代码
cpp
#include <iostream>
#include <string>
using namespace std;
class Solution
{
public:
bool isUnique(string astr)
{
//鸽巢原理优化:小写字母只有26个,长度>26一定重复
if (astr.size() > 26)
return false;
//用int的32个比特位模拟位图,存储26个小写字母的出现状态
int bitMap = 0;
for (auto ch : astr)
{
//计算字符对应的比特位下标(a=0, b=1...z=25)
int i = ch - 'a';
//右移i位后与1相与,判断该位是否已被占用(字符重复)
if (((bitMap >> i) & 1) == 1)
return false;
//将对应比特位设为1,标记该字符已出现
bitMap |= 1 << i;
}
//遍历完成,无重复字符
return true;
}
};
int main() {
Solution sol;
string s1 = "";
cout << "输入:\"" << s1 << "\" 输出:" << boolalpha << sol.isUnique(s1) << endl;
string s2 = "abc";
cout << "输入:\"" << s2 << "\" 输出:" << boolalpha << sol.isUnique(s2) << endl;
string s3 = "leetcode";
cout << "输入:\"" << s3 << "\" 输出:" << boolalpha << sol.isUnique(s3) << endl;
string s4 = "abcdefghijklmnopqrstuvwxyz";
cout << "输入:\"" << s4 << "\" 输出:" << boolalpha << sol.isUnique(s4) << endl;
string s5 = "abcdefghijklmnopqrstuvwxyza";
cout << "输入:\"" << s5 << "\" 输出:" << boolalpha << sol.isUnique(s5) << endl;
return 0;
}

3.丢失的数字(OJ题)

算法思路:解法(位运算)
设数组的大小为 n,那么缺失之前的数就是 [0, n],数组中是在 [0, n] 中缺失一个数形成的序列.
如果我们把数组中的所有数,以及 [0, n] 中的所有数全部异或在一起,那么根据异或运算的消消乐规律,最终的异或结果应该就是缺失的数~
核心代码
cpp
class Solution {
public:
int missingNumber(vector<int>& nums) {
int ret = 0;
for (auto x : nums)
ret ^= x;
for (int i = 0; i <= nums.size(); i++)
ret ^= i;
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int missingNumber(vector<int>& nums) {
int ret = 0;
//第一步:异或数组中所有元素
for (auto x : nums)
ret ^= x;
//第二步:异或 0 ~ nums.size() 的所有整数
for (int i = 0; i <= nums.size(); i++)
ret ^= i;
//最终结果就是缺失的数字(重复出现的数会异或抵消为0)
return ret;
}
};
int main() {
Solution sol;
vector<int> nums1 = {3, 0, 1};
cout << "测试用例1:nums = [3,0,1] → 缺失数字:" << sol.missingNumber(nums1) << endl;
vector<int> nums2 = {0, 1};
cout << "测试用例2:nums = [0,1] → 缺失数字:" << sol.missingNumber(nums2) << endl;
vector<int> nums3 = {9,6,4,2,3,5,7,0,1};
cout << "测试用例3:nums = [9,6,4,2,3,5,7,0,1] → 缺失数字:" << sol.missingNumber(nums3) << endl;
vector<int> nums4 = {0};
cout << "测试用例4:nums = [0] → 缺失数字:" << sol.missingNumber(nums4) << endl;
vector<int> nums5 = {1};
cout << "测试用例5:nums = [1] → 缺失数字:" << sol.missingNumber(nums5) << endl;
return 0;
}

4.两整数之和(OJ题)

算法思路:解法(位运算)
- 异或
^运算本质是无进位加法; - 按位与
&操作能够得到进位; - 然后一直循环进行,直到进位变成
0为止.

核心代码
cpp
class Solution {
public:
int getSum(int a, int b) {
while (b != 0) {
int x = a ^ b; //先算出⽆进位相加的结果
unsigned int carry = (unsigned int)(a & b) << 1; //算出进位
a = x;
b = carry;
}
return a;
}
};
完整测试代码
cpp
#include <iostream>
using namespace std;
class Solution {
public:
int getSum(int a, int b) {
while (b != 0) {
int x = a ^ b; //无进位加法结果
unsigned int carry = (unsigned int)(a & b) << 1; //计算进位(无符号避免溢出)
a = x;
b = carry;
}
return a;
}
};
int main() {
Solution sol;
cout << "3 + 1 = " << sol.getSum(3, 1) << endl;
cout << "2 + (-3) = " << sol.getSum(2, -3) << endl;
cout << "-1 + (-2) = " << sol.getSum(-1, -2) << endl;
cout << "5 + 0 = " << sol.getSum(5, 0) << endl;
cout << "6 + (-6) = " << sol.getSum(6, -6) << endl;
cout << "-1000 + 1000 = " << sol.getSum(-1000, 1000) << endl;
return 0;
}

5.只出现一次的数字||(OJ题)

算法思路:解法(比特位计数)
设要找的数位 ret.
由于整个数组中,需要找的元素只出现了一次,其余的数都出现的三次,因此我们可以根据所有数的某一个比特位的总和 %3 的结果,快速定位到 ret 的一个比特位上的值是 0 还是 1.这样,我们通过 ret 的每一个比特位上的值,就可以将 ret 给还原出来.
核心代码
cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (int i = 0; i < 32; i++) //依次去修改ret中的每⼀位
{
int sum = 0;
for (int x : nums) //计算nums中所有的数的第i位的和
if (((x >> i) & 1) == 1)
sum++;
sum %= 3;
if (sum == 1)
ret |= 1 << i;
}
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
//遍历int的32个二进制位(0~31位)
for (int i = 0; i < 32; i++)
{
int sum = 0;
//统计所有数字第i位为1的总个数
for (int x : nums)
if (((x >> i) & 1) == 1)
sum++;
//对3取余,余数为1说明目标数的第i位是1
sum %= 3;
if (sum == 1)
ret |= 1 << i; // 将ret的第i位设为1
}
return ret;
}
};
int main() {
Solution sol;
vector<int> nums1 = {2,2,3,2};
cout << "测试用例1:[2,2,3,2] → 只出现一次的数字:" << sol.singleNumber(nums1) << endl;
vector<int> nums2 = {0,1,0,1,0,1,99};
cout << "测试用例2:[0,1,0,1,0,1,99] → 只出现一次的数字:" << sol.singleNumber(nums2) << endl;
vector<int> nums3 = {-2,-2,1,1,4,1,4,4,-3,-2};
cout << "测试用例3:[-2,-2,1,1,4,1,4,4,-3,-2] → 只出现一次的数字:" << sol.singleNumber(nums3) << endl;
vector<int> nums4 = {5,5,5,7};
cout << "测试用例4:[5,5,5,7] → 只出现一次的数字:" << sol.singleNumber(nums4) << endl;
return 0;
}

6.消失的两个数字(OJ题)

算法思路:解法(位运算)
本题就是丢失的数字+只出现一次的数字III组合起来的题.大家可以回顾一下这两道题的算法思想.
先将数组中的数和 [1, n + 2] 区间内的所有数异或在一起,问题就变成了:有两个数出现了一次,其余所有的数出现了两次.进而变成了只出现一次的数字 III 这道题.
核心代码
cpp
class Solution {
public:
vector<int> missingTwo(vector<int>& nums) {
//1.将所有的数异或在⼀起
int tmp = 0;
for (auto x : nums)
tmp ^= x;
for (int i = 1; i <= nums.size() + 2; i++)
tmp ^= i;
//2.找出 a,b 中⽐特位不同的那⼀位
int diff = 0;
while (1) {
if (((tmp >> diff) & 1) == 1)
break;
else
diff++;
}
//3.根据diff位的不同,将所有的数划分为两类来异或
int a = 0, b = 0;
for (int x : nums)
if (((x >> diff) & 1) == 1)
b ^= x;
else
a ^= x;
for (int i = 1; i <= nums.size() + 2; i++)
if (((i >> diff) & 1) == 1)
b ^= i;
else
a ^= i;
return {a, b};
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> missingTwo(vector<int>& nums) {
//1.将所有的数异或在一起
int tmp = 0;
for (auto x : nums)
tmp ^= x;
for (int i = 1; i <= nums.size() + 2; i++)
tmp ^= i;
//2.找出 a,b 中比特位不同的那一位
int diff = 0;
while (1) {
if (((tmp >> diff) & 1) == 1)
break;
else
diff++;
}
//3.根据diff位的不同,将所有的数划分为两类来异或
int a = 0, b = 0;
for (int x : nums)
if (((x >> diff) & 1) == 1)
b ^= x;
else
a ^= x;
for (int i = 1; i <= nums.size() + 2; i++)
if (((i >> diff) & 1) == 1)
b ^= i;
else
a ^= i;
return {a, b};
}
};
int main() {
Solution sol;
vector<int> nums1;
vector<int> res1 = sol.missingTwo(nums1);
cout << "测试用例1:nums = [] → 缺失的两个数:" << res1[0] << " " << res1[1] << endl;
vector<int> nums2 = {1};
vector<int> res2 = sol.missingTwo(nums2);
cout << "测试用例2:nums = [1] → 缺失的两个数:" << res2[0] << " " << res2[1] << endl;
vector<int> nums3 = {2,3};
vector<int> res3 = sol.missingTwo(nums3);
cout << "测试用例3:nums = [2,3] → 缺失的两个数:" << res3[0] << " " << res3[1] << endl;
vector<int> nums4 = {1,2,3,5};
vector<int> res4 = sol.missingTwo(nums4);
cout << "测试用例4:nums = [1,2,3,5] → 缺失的两个数:" << res4[0] << " " << res4[1] << endl;
return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容:【优选算法】(实战推演模拟算法的蕴含深意)
每日心灵鸡汤:坦然接受一切,允许一切发生!
总有一天你会明白,"生命中最强大的力量,不是对抗,而是接受."人活着,总会经历许多坎坷与挫折,有的人选择勇敢接受,坦然面对;有的人却选择惶恐逃避,或怨天尤人,或一蹶不振.其实,面对生命里的无常与艰难,与其焦虑和烦恼,不如放平心态,试着去允许和接受.凡事不计较,不强求,不对峙,不取悦,不迎合,不执念,允许一切如其所是,允许自己做自己,也允许别人做别人;接纳不可更改之事,看淡、看开,然后全身心投入当下生活,去遇见更多的美好和诗意.真正的强大,是允许一切发生;人生最高的境界,是接纳一切无常的变化.生活不过是见招拆招,经事炼心,你要做的就是修炼强大的内心,随时做好允许和接纳的准备.请相信,一切都是最好的安排!
