须知
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++算法感兴趣的朋友,让我们一起进步!
位运算(Bitwise Operation)是计算机底层操作的核心。它的重要性体现在以下几个方面:
位运算是对整数在二进制位层面进行操作的运算,主要包括以下基本操作:
1 时,结果为 1;否则为 0。1101 & 1011 = 10011,结果为 1;否则为 0。1。 1101 | 1011 = 11110,不同为 1。1101 ^ 1011 = 01101 变 0,0 变 1。~1101 = 0010(假设为4位操作)0。2^n。 1101 << 2 = 1101000 填充。2^n。 1101 >> 2 = 0011n & 1 判断奇偶。 5 & 1 = 1(奇数),4 & 1 = 0(偶数)a = a ^ b b = a ^ b a = a ^ b
a = 5, b = 7 -> a = 7, b = 5n & (n - 1)1 的个数。 12 (1100) -> n & (n - 1) = 8 (1000)n > 0 and (n & (n - 1)) == 0 8 (1000) 是 2 的幂;10 (1010) 不是。111 表示 rwx 权限,101 表示 rw-。a ^ b(不进位加法)和 a & b(进位)。位运算的基础简单但应用广泛,掌握它不仅能提升代码效率,还能帮助开发者深入理解计算机的工作原理。
题目链接:191. 位1的个数 - 力扣(LeetCode)
题目描述:

2.1 算法思路:
算法核心思想
>>)操作,将 n 的最低位逐步移出,然后与 1 按位与(&)操作来检查这一位是否为 1。1,说明当前位是 1,计数器 count 增加 1。int),需要从最低位到最高位依次检查所有 32 位。1 时,计数器增加,最终返回这个计数值。class Solution
{
public:
int hammingWeight(int n)
{
int count = 0; // 初始化计数器,统计 1 的个数
for (int i = 0; i < 32; i++) // 遍历 32 位
{
if (((n >> i) & 1) == 1) // 右移 i 位后,与 1 按位与,判断最低位是否为 1
count++; // 如果是 1,增加计数
}
return count; // 返回汉明重量
}
};输入:n = 11
二进制表示:00000000000000000000000000001011
逐步过程如下:
i = 0: (n >> 0) & 1 = 000...0011 & 1 = 1 -> count = 1i = 1: (n >> 1) & 1 = 000...001 & 1 = 1 -> count = 2i = 2: (n >> 2) & 1 = 000...000 & 1 = 0 -> count = 2i = 3: (n >> 3) & 1 = 000...000 & 1 = 1 -> count = 3
最终结果:count = 3count,因此是 O(1)。虽然代码的时间复杂度已经是 O(1),但在某些场景下可以进一步优化位操作的效率,例如使用 n & (n - 1) 清零最低位的 1。
优化后的代码:
class Solution
{
public:
int hammingWeight(int n)
{
int count = 0;
while (n != 0) // 当 n 不为 0 时,逐位清零最低位的 1
{
n &= (n - 1); // 清零最低位的 1
count++;
}
return count;
}
};1 清零,使得循环次数仅等于 1 的个数。k 是 1 的个数,适用于输入值中 1 的个数远小于 32 的情况。1 的个数较少)的场景。题目链接:338. 比特位计数 - 力扣(LeetCode)
题目描述:

hammingWeight 的功能:
n,计算其二进制中 1 的个数。n 的每一位是否为 1,如果是,则计数器增加。>> 将数字逐位右移。1 进行按位与(&)运算,检查最低位是否为 1。countBits 的功能:
0 到 n,依次调用 hammingWeight 计算每个数字中 1 的个数,并将结果存入数组 arr。i,范围是 [0, n]。i 调用 hammingWeight,获取其二进制中 1 的个数。arr.push_back(count) 将结果依次存入数组。arr 用于存储结果。0 到 n,计算每个数字的 1 个数。arr。// 定义一个辅助函数,用于计算一个整数的二进制中 '1' 的个数
int hammingWeight(int n)
{
int count = 0; // 初始化计数器,用于统计二进制中 '1' 的数量
for (int i = 0; i < 32; i++) // 遍历整数的 32 位(假设输入是 32 位整数)
{
// 右移 i 位,并用按位与操作提取最低位,检查是否为 1
if (((n >> i) & 1) == 1)
count++; // 如果当前位是 1,计数器加 1
}
return count; // 返回 '1' 的总个数
}
// 定义一个类,包含主函数,用于生成从 0 到 n 每个数字的二进制中 '1' 的个数
class Solution
{
public:
// 主函数,生成结果数组
vector<int> countBits(int n)
{
vector<int> arr; // 定义一个向量用于存储结果,每个元素代表对应数字的二进制中 '1' 的个数
int count1 = 0; // 临时变量,用于存储每个数字的 '1' 个数
// 遍历从 0 到 n 的每个数字
for (int i = 0; i <= n; i++)
{
count1 = hammingWeight(i); // 调用辅助函数,计算当前数字的 '1' 个数
arr.push_back(count1); // 将结果添加到结果数组中
}
return arr; // 返回结果数组
}
};辅助函数 hammingWeight
int hammingWeight(int n)
{
int count = 0;
for (int i = 0; i < 32; i++) // 遍历整数的 32 位
{
if (((n >> i) & 1) == 1) // 检查当前位是否为 1
count++; // 如果是,增加计数
}
return count; // 返回 1 的个数
} 主函数 countBits
class Solution
{
public:
vector<int> countBits(int n)
{
vector<int> arr; // 用于存储每个数字的汉明重量
int count1 = 0;
for (int i = 0; i <= n; i++) // 遍历从 0 到 n 的每个数字
{
count1 = hammingWeight(i); // 计算当前数字的二进制中 1 的个数
arr.push_back(count1); // 将结果加入数组
}
return arr; // 返回结果数组
}
};示例分析
示例输入:n = 5
目标:生成从 0 到 5 的二进制中 1 的个数,即:
0 -> 0000 → 1 的个数 = 01 -> 0001 → 1 的个数 = 12 -> 0010 → 1 的个数 = 13 -> 0011 → 1 的个数 = 24 -> 0100 → 1 的个数 = 15 -> 0101 → 1 的个数 = 2输出:[0, 1, 1, 2, 1, 2]
逐步过程:
i = 0 → 调用 hammingWeight(0) → 结果 0 → arr = [0]i = 1 → 调用 hammingWeight(1) → 结果 1 → arr = [0, 1]i = 2 → 调用 hammingWeight(2) → 结果 1 → arr = [0, 1, 1]i = 3 → 调用 hammingWeight(3) → 结果 2 → arr = [0, 1, 1, 2]i = 4 → 调用 hammingWeight(4) → 结果 1 → arr = [0, 1, 1, 2, 1]i = 5 → 调用 hammingWeight(5) → 结果 2 → arr = [0, 1, 1, 2, 1, 2]最终返回 arr = [0, 1, 1, 2, 1, 2]。
时间复杂度:
0 到 n 遍历了 n + 1 个数字。hammingWeight 执行了 32 次操作(检查 32 位)。O(n * 32),即 O(32n),等价于 O(n)。空间复杂度:
arr,大小为 n + 1。当前实现中,每次计算每个数字的汉明重量时,完全独立地逐位检查。可以通过 动态规划 优化,利用前一个数字的结果推导当前数字的汉明重量。优化方法为:
i 是偶数,则 countBits(i) = countBits(i / 2)(右移一位,相当于去掉最低位)。i 是奇数,则 countBits(i) = countBits(i / 2) + 1(最低位为 1)。class Solution
{
public:
vector<int> countBits(int n)
{
vector<int> dp(n + 1, 0); // 初始化结果数组,dp[i] 表示数字 i 的汉明重量
for (int i = 1; i <= n; i++)
{
dp[i] = dp[i >> 1] + (i & 1); // 动态转移方程
}
return dp;
}
};题目描述:

算法分为两部分
hammingWeight:
1 的个数(即汉明重量,Hamming Weight)。>>)和按位与(&)操作,统计 1 的个数。hammingDistance:
x 和 y 的汉明距离。^)操作找出 x 和 y 的二进制表示中不同的位,结果是一个新整数 s。hammingWeight 函数统计 s 中的 1 的个数,即为汉明距离。// 辅助函数:计算整数 n 的二进制表示中 '1' 的个数
int hammingWeight(int n)
{
int count = 0; // 初始化计数器,用于统计二进制中 '1' 的数量
for (int i = 0; i < 32; i++) // 遍历 32 位(假设输入为 32 位整数)
{
// 右移 i 位,并与 1 进行按位与操作,检查最低位是否为 1
if (((n >> i) & 1) == 1)
count++; // 如果最低位为 1,则计数器加 1
}
return count; // 返回二进制中 '1' 的总数
}
// 主类:用于计算两个整数的汉明距离
class Solution
{
public:
// 主函数:计算 x 和 y 的汉明距离
int hammingDistance(int x, int y)
{
// 按位异或操作,得到二进制中 x 和 y 不同的位
// 异或规则:相同位结果为 0,不同位结果为 1
int s = x ^ y;
// 调用辅助函数 hammingWeight,统计异或结果中 '1' 的个数
// 这些 '1' 的数量即为 x 和 y 的汉明距离
return hammingWeight(s);
}
};hammingWeight:
n 的二进制表示中 1 的个数。1。 n >> i:将整数 n 右移 i 位。(n >> i) & 1:提取右移后结果的最低位,并判断是否为 1。hammingDistance:
x 和 y 的汉明距离。x ^ y)操作,生成一个新的整数 s,其二进制表示中 1 的位置对应 x 和 y 不同的位。 1 ^ 1 = 0 0 ^ 0 = 0 1 ^ 0 = 1 0 ^ 1 = 1
s 的二进制中所有的 1 表示 x 和 y 不同的位。hammingWeight 函数,统计 s 中 1 的个数,作为 x 和 y 的汉明距离。hammingWeight 时间复杂度为 O(32),即 O(1)。示例运行过程
示例输入
x = 1, y = 4
计算过程
x 的二进制表示:0001
y 的二进制表示:0100
按位异或:
x ^ y = 0001 ^ 0100 = 0101
0101。
hammingWeight(5):
5 的二进制表示为:0101。1 → count = 10 → count = 11 → count = 20 → count = 2count = 2。2
输出
2
时间复杂度
hammingWeight:遍历 32 位,时间复杂度为 O(1)。空间复杂度
count 和 s),不需要额外空间,O(1)。hammingWeight 函数,利用 n & (n - 1) 方法快速清除最低位的 1,减少迭代次数优化后的辅助函数
int hammingWeight(int n)
{
int count = 0;
while (n != 0) // 当 n 不为 0 时
{
n &= (n - 1); // 清除最低位的 1
count++; // 每次清除后计数加 1
}
return count; // 返回二进制中 '1' 的总数
}1 的个数,适用于二进制中 1 较少的情况,进一步提升性能。完整优化代码
int hammingWeight(int n)
{
int count = 0;
while (n != 0)
{
n &= (n - 1);
count++;
}
return count;
}
class Solution
{
public:
int hammingDistance(int x, int y)
{
int s = x ^ y; // 按位异或,找出 x 和 y 不同的位
return hammingWeight(s); // 统计不同位的数量(即 '1' 的个数)
}
};题目链接:136. 只出现一次的数字 - 力扣(LeetCode)
题目描述:

a ^ a = 0(任何数与自己异或,结果为 0)a ^ 0 = a(任何数与 0 异或,结果不变)a ^ b ^ c = a ^ c ^ b。0。0 异或后仍是其本身。class Solution
{
public:
int singleNumber(vector<int>& nums)
{
int ret = 0; // 初始化结果为 0
for (auto s : nums) // 遍历数组中的每个元素
{
ret ^= s; // 异或操作:逐个元素与结果异或
}
return ret; // 返回出现一次的数字
}
};输入示例
nums = [4, 1, 2, 1, 2]
执行过程

最终结果:4
ret 进行累加异或,不需要额外的数据结构。思路
代码实现
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int, int> countMap;
for (int num : nums) {
countMap[num]++; // 记录每个元素出现的次数
}
for (auto& pair : countMap) {
if (pair.second == 1) { // 找到只出现一次的元素
return pair.first;
}
}
return -1; // 兜底返回
}
};时间复杂度:O(n) — 遍历数组和哈希表
空间复杂度:O(n) — 额外使用哈希表存储元素及其频次
思路
代码实现
class Solution {
public:
int singleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 排序数组
for (int i = 0; i < nums.size(); i += 2) { // 每次跳两个位置
if (i == nums.size() - 1 || nums[i] != nums[i + 1]) {
return nums[i]; // 如果元素和下一个元素不同,返回该元素
}
}
return -1; // 兜底返回
}
};时间复杂度:O(n log n) — 排序的时间复杂度
空间复杂度:O(1) — 如果排序算法为原地排序
思路
代码实现
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_set<int> numSet;
int sumOfNums = 0, sumOfSet = 0;
for (int num : nums) {
sumOfNums += num; // 数组中所有元素的和
if (numSet.find(num) == numSet.end()) { // 如果元素不在集合中
numSet.insert(num);
sumOfSet += num; // 记录集合中元素的和
}
}
return 2 * sumOfSet - sumOfNums; // 根据公式计算结果
}
};时间复杂度:O(n) — 遍历数组
空间复杂度:O(n) — 额外使用集合

a ^ a = 0),最终只剩下不重复的那个数。题目链接:260. 只出现一次的数字 III - 力扣(LeetCode)
题目描述:

思路解析
步骤1:使用异或找出两个数字的异或结果
xorsum 是这两个不同数字的异或结果。 0(a ^ a = 0)。a ^ 0 = a)。xorsum 的特点: xorsum = num1 ^ num2,其中 num1 和 num2 是结果。
步骤2:找到异或结果中最低的不同位(区分两组数字)
xorsum 中的 最低位的1,表示在这一位上,两个不同数字的二进制表示不同(一个是 1,另一个是 0)。
xorsum & (-xorsum) 找出最低的1位: (-xorsum) 是 xorsum 的 补码,补码中保留最低位的1并将其他位取反。xorsum & (-xorsum) 可以快速找到最低位的1。lsb(lowest significant bit)可以将数组中的所有数字分为两组:
1 的数字。0 的数字。步骤3:分别异或两组数字,得到两个结果
num1。num2。class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int xorsum = 0; // 用于存储所有数字的异或结果
for (int num: nums) {
xorsum ^= num; // 所有数字异或,最终结果是两个不同数字的异或值
}
// 找出 xorsum 中最低位的 1(用于区分两个数字)
// 如果 xorsum 是 INT_MIN(最小负数),它本身只有最高位为 1,不会溢出
int lsb = (xorsum == INT_MIN ? xorsum : xorsum & (-xorsum));
int type1 = 0, type2 = 0; // 用于存储两个只出现一次的数字
for (int num: nums) {
if (num & lsb) { // 根据最低位的1分组:该位为1
type1 ^= num; // 第一组异或,得到 num1
}
else { // 该位为0
type2 ^= num; // 第二组异或,得到 num2
}
}
return {type1, type2}; // 返回结果
}
};输入
nums = [1, 2, 1, 3, 2, 5]
步骤1:计算所有元素的异或结果
1 ^ 2 ^ 1 ^ 3 ^ 2 ^ 5 = 3 ^ 5 = 6(二进制 0110)6 是 3 和 5 的异或结果。步骤2:找到最低位的1
xorsum = 6 → 二进制 0110xorsum & (-xorsum) = 2(最低位的1在第2位)。步骤3:根据最低位的1分组
2, 2, 31, 1, 5步骤4:分别异或两组
2 ^ 2 ^ 3 = 31 ^ 1 ^ 5 = 5输出
[3, 5](结果顺序不唯一)。
时间复杂度分析
空间复杂度分析
思路
代码实现
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
unordered_map<int, int> freq; // 统计元素出现的频率
for (int num : nums) {
freq[num]++;
}
vector<int> result;
for (auto& pair : freq) {
if (pair.second == 1) { // 出现次数为1的元素
result.push_back(pair.first);
}
}
return result;
}
};复杂度分析
思路
代码实现
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 排序数组
vector<int> result;
for (int i = 0; i < nums.size(); i++) {
if ((i == 0 || nums[i] != nums[i - 1]) &&
(i == nums.size() - 1 || nums[i] != nums[i + 1])) {
result.push_back(nums[i]);
}
}
return result;
}
};复杂度分析

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