须知
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++算法感兴趣的朋友,让我们一起进步!
接上篇:【优选算法篇】位运算小课堂:从入门到精通的奇妙之旅(上篇)-CSDN博客
引言:通过上篇文章带大家简单了解“位运算算法”,小试牛刀。接下来将让大家感受一下位运算在解题的妙处。
num & 1。总结:掌握位运算不仅可以帮助快速通过面试常见题型,更能展示对计算机底层机制的理解和高效解决问题的能力。
前言
位运算是计算机程序设计中的基础且高效的运算方式,直接对二进制位进行操作,适合解决性能敏感场景下的复杂问题。
位运算是对整数的二进制表示进行按位操作的运算,包括:
基本运算
1,结果为 1,否则为 0。5 & 3 = 0101 & 0011 = 0001 (1)1,结果为 1。5 | 3 = 0101 | 0011 = 0111 (7)0,不同为 1。5 ^ 3 = 0101 ^ 0011 = 0110 (6)0 变 1,1 变 0。~5 = ~0101 = 1010(假设4位)0。x * 2^n。 5 << 1 = 0101 << 1 = 1010 (10)x / 2^n。 5 >> 1 = 0101 >> 1 = 0010 (2)方法:通过按位与操作 n & 1。
1。0。bool isOdd(int n) { return n & 1; // 返回 true 表示奇数 }
方法:使用异或运算 ^。
void swap(int &a, int &b) { a ^= b; b ^= a; a ^= b; }
方法:使用按位与操作 n & (1 << i),其中 i 是目标位的位置
bool isBitSet(int n, int i) { return n & (1 << i); }
方法:n & (n - 1)
n - 1 将最低位的 1 变为 0,并将该位之后的所有位取反。int countOnes(int n) { int count = 0; while (n != 0) { n &= (n - 1); // 清零最低位的1 count++; } return count; }
方法:通过左移操作 n << k。
n << k 等价于 n * 2^kint powerOfTwo(int n, int k) { return n << k; // n 乘以 2^k }
应用:将某个位设为1、清零、反转。
n | (1 << i)n & ~(1 << i)n ^ (1 << i)int setBit(int n, int i) { return n | (1 << i); } // 设置第i位为1 int clearBit(int n, int i) { return n & ~(1 << i); } // 清除第i位 int toggleBit(int n, int i) { return n ^ (1 << i); } // 反转第i位
位运算 是计算机底层运算的基石,掌握它能够高效解决许多算法和面试题目。
题目链接:面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)
题目描述:

2.1 算法思路:
int ret 的 26 位来表示每个字符是否出现过。i 位为 1:表示字符 'a' + i 已经出现过。i 位为 0:表示字符 'a' + i 尚未出现。i = s - 'a'。(ret >> i) & 1 判断该位是否为 1: false。ret |= (1 << i) 将该位设置为 1。true。26,必然有重复字符,可以直接返回 false。class Solution
{
public:
bool isUnique(string astr)
{
// 如果字符串长度大于26,字符必然重复
if (astr.size() > 26)
return false;
int ret = 0; // 用于存储字符出现情况的位掩码
for (auto s : astr)
{
int i = s - 'a'; // 计算当前字符对应的位置(0~25)
// 检查第 i 位是否已经为 1(字符是否已出现)
if ((ret >> i) & 1)
return false;
// 将第 i 位设置为 1,标记字符已出现
ret |= (1 << i);
}
return true; // 所有字符唯一
}
};输入:"abc"
ret = 0(所有位为0)。| 字符 | 位置 i | 检查 (ret >> i) & 1 | 设置 ret |= (1 << i) | ret (二进制) | |------|---------|------------------------|-----------------------|----------------| | 'a'| 0 | 0 | 00000000000000000001 | 00000001 | | 'b'| 1 | 0 | 00000000000000000010 | 00000011 | | 'c'| 2 | 0 | 00000000000000000100 | 00000111 |
结果:所有字符唯一,返回 true。输入:"abca"
'a' 时: (ret >> 0) & 1 = 1,说明字符 'a' 已经出现过,返回 false。思路
false。true。代码实现
class Solution {
public:
bool isUnique(string astr) {
unordered_set<char> charSet; // 哈希集合用于存储字符
for (char ch : astr) {
if (charSet.count(ch)) { // 字符已存在
return false;
}
charSet.insert(ch); // 插入字符
}
return true; // 所有字符唯一
}
};时间复杂度:O(n),其中 n 为字符串长度。
空间复杂度:O(n),最坏情况下存储所有字符。
思路
false。true。代码实现
class Solution {
public:
bool isUnique(string astr) {
int n = astr.size();
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (astr[i] == astr[j]) { // 字符重复
return false;
}
}
}
return true; // 所有字符唯一
}
};时间复杂度:O(n^2),其中 n 为字符串长度。
空间复杂度:O(1),无额外空间使用。
思路
false。true。代码实现
#include <algorithm>
class Solution {
public:
bool isUnique(string astr) {
sort(astr.begin(), astr.end()); // 字符串排序
for (int i = 1; i < astr.size(); i++) {
if (astr[i] == astr[i - 1]) { // 检查相邻字符是否相等
return false;
}
}
return true; // 所有字符唯一
}
};思路
true,说明重复。
true。
代码实现
class Solution {
public:
bool isUnique(string astr) {
bool charSet[256] = {false}; // ASCII字符集
for (char ch : astr) {
if (charSet[ch]) { // 字符已存在
return false;
}
charSet[ch] = true; // 标记字符
}
return true; // 所有字符唯一
}
};时间复杂度:O(n),其中 n 为字符串长度。
空间复杂度:O(1),使用固定大小的数组(256)。

2.4 时间复杂度与空间复杂度
n 是字符串的长度。
int 变量 ret 来存储字符的状态(固定大小)。a-z)。int 可以高效记录字符出现情况。题目链接:268. 丢失的数字 - 力扣(LeetCode)
题目描述:

3.1 算法思路:位运算(异或法)
a ^ a = 0(相同的数异或为0)。a ^ 0 = a(任何数与0异或,结果为它本身)。a ^ b ^ c = a ^ c ^ b。0 到 n 的所有数字,除了缺失的一个数字。0 ~ n 所有数字的异或结果与数组中的所有数字的异或结果再次异或,它们成对的数字会互相抵消为 0,只剩下缺失的数字。0 ~ n 范围内的所有数字进行异或。0,剩下的即为 缺失的数字。class Solution {
public:
int missingNumber(vector<int>& nums) {
int res = 0; // 初始化结果
int n = nums.size();
// 异或数组中的所有数字
for (int i = 0; i < n; i++) {
res ^= nums[i];
}
// 异或 0 ~ n 范围内的所有数字
for (int i = 0; i <= n; i++) {
res ^= i;
}
return res; // 返回最终结果,即缺失的数字
}
};假设输入:nums = [3, 0, 1]
数组缺失的数字是 2,我们执行以下步骤:
res = 0 res ^= 3 → res = 3 res ^= 0 → res = 3 ^ 0 = 3 res ^= 1 → res = 3 ^ 1 = 2
2. 异或 0 ~ n 范围内的所有数字(n = 3):
res ^= 0 → res = 2 ^ 0 = 2 res ^= 1 → res = 2 ^ 1 = 3 res ^= 2 → res = 3 ^ 2 = 1 res ^= 3 → res = 1 ^ 3 = 2
最终结果 res = 2,缺失的数字即为 2。
O(n)。0 ~ n 一次:O(n)。O(n)。res 记录异或结果,空间复杂度为 O(1)。优点总结
思路
sum = n * (n + 1) / 2,计算 0 ~ n 的总和。sum - actualSum。代码实现
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
int totalSum = n * (n + 1) / 2; // 0~n 的总和
int arraySum = 0;
for (int num : nums) {
arraySum += num; // 计算数组元素的总和
}
return totalSum - arraySum; // 两者之差就是缺失的数字
}
};时间复杂度:O(n)
空间复杂度:O(1)
思路
i 和数组元素 nums[i] 不相等,说明缺失的数字是 i。
n(最大值)。
代码实现
#include <algorithm>
class Solution {
public:
int missingNumber(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 排序数组
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != i) { // 如果索引与元素不匹配
return i;
}
}
return nums.size(); // 缺失的是 n
}
};思路
0~n 范围内的所有数字,判断每个数字是否存在于哈希表中。
代码实现
#include <unordered_set>
class Solution {
public:
int missingNumber(vector<int>& nums) {
unordered_set<int> numSet(nums.begin(), nums.end()); // 将数组存入哈希表
for (int i = 0; i <= nums.size(); i++) {
if (numSet.find(i) == numSet.end()) { // 如果数字不在哈希表中
return i;
}
}
return -1; // 理论上不会执行到这里
}
};时间复杂度:O(n)
空间复杂度:O(n)
思路
n+1)中。代码实现
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
vector<bool> mark(n + 1, false); // 辅助数组,初始化为false
for (int num : nums) {
mark[num] = true; // 标记出现的数字
}
for (int i = 0; i <= n; i++) {
if (!mark[i]) { // 找到未标记的位置
return i;
}
}
return -1; // 理论上不会执行到这里
}
};时间复杂度:O(n)
空间复杂度:O(n)

异或法 是解决 缺失数字 问题的最优解之一,利用位运算性质,简单高效,时间复杂度为 O(n),空间复杂度为 O(1)。
1 的个数,可以解决 K次出现问题。通过上面几个例题:「判定字符是否唯一」的多种解法、以及「丢失的数字」的位运算方法 我们总结出位运算在数组问题中的高效应用。 位运算 通过将复杂的数字出现次数、分组、去重等问题转化为简单的位级操作(如异或、位统计、取模等),极大地提升了问题求解的效率。 在处理 元素重复出现问题、分组求解问题 以及 高效数值处理 等场景时,位运算展现出显著的性能优势,尤其适用于 大规模数据 和 时间复杂度敏感 的应用场景。
路虽远,行则将至;事虽难,做则必成
亲爱的读者们,下一篇文章再会!!!