位运算完整学习笔记:按位与(&)+ 异或(^)

从核心原理到算法应用全梳理

位运算直接对整数的二进制位执行操作,具备常数级时间、O(1)额外空间的天然优势,是算法优化、底层编程的核心工具。其中按位与(&)和异或(^)是最核心的两个运算符:& 擅长筛选、提取、算进位;^ 擅长抵消、对比、做还原。二者独立可解决基础题,组合可覆盖绝大多数进阶位运算题型。


一、按位与运算(&)

1. 核心原理

定义

对两个整数的二进制逐位对齐运算,规则:同一位上两个数都是 1,结果位才是 1;只要有一个是 0,结果位就是 0。

真值表
位A 位B A & B 结果
0 0 0
0 1 0
1 0 0
1 1 1
本质

按位与的核心是掩码筛选:构造一个只有目标位为1的「掩码」,和原数做与运算,即可精准保留目标位、清零其余所有位,实现对二进制位的精准控制。

核心性质
  • 交换律:a & b = b & a
  • 结合律:(a & b) & c = a & (b & c)
  • 幂等律:a & a = a(自身与自身运算结果不变)
  • 零律:a & 0 = 0(和0运算,所有位全部清零)

2. 触发信号:什么时候想到用 &

题目出现以下特征时,优先考虑按位与运算:

  • 需要判断二进制某一位是 0 还是 1
  • 需要保留指定二进制位、清零其他所有位
  • 需要计算二进制加法的进位值
  • 判断奇偶、校验状态标志位

3. 经典应用场景

场景1:位状态判断(代表:判断整数奇偶)
为什么能这么做

构造一个「只有目标位是1、其余全是0」的掩码,和原数做按位与:

  • 结果 ≠ 0 → 目标位为 1
  • 结果 = 0 → 目标位为 0
代码示例
cpp 复制代码
// 判断整数奇偶:最低位为1则是奇数
bool isOdd(int num) {
    return (num & 1) == 1;
}

// 判断第k位是否为1
bool isBitSet(int num, int k) {
    return (num & (1 << k)) != 0;
}
对应题目/场景
  • 基础语法:奇偶判断、标志位校验
  • 力扣461. 汉明距离:逐位统计1的个数

场景2:提取指定位,清零其余位(代表:提取最右侧的1)
为什么能这么做

掩码中为1的位置,与运算后保留原值;掩码中为0的位置,与运算后强制清零。 经典技巧 num & (~num + 1) 依托整数补码规则:对原数取反加1后,仅在原数最右侧1的位置为1,其余位全为0;和原数按位与后,最终只保留最右侧的1。

代码示例
cpp 复制代码
// 提取num二进制最右侧的1,其余位清零
int getRightmostOne(int num) {
    return num & (~num + 1);
}
对应题目/场景
  • 力扣260. 只出现一次的数字 III:提取分组标记位
  • 状态位提取、标志位筛选

场景3:计算二进制加法的进位
为什么能这么做

二进制加法中,只有 1 + 1 才会向高位产生进位,刚好匹配按位与「同1才1」的规则。进位需要进到高位,因此结果左移1位。

代码示例
cpp 复制代码
// 计算a + b产生的进位总值
int getCarry(int a, int b) {
    return (a & b) << 1;
}
对应题目/场景
  • 力扣371. 两整数之和:不用加减号实现加法

二、异或运算(^)

1. 核心原理

定义

对两个整数的二进制逐位对齐运算,核心规则:相同为0,不同为1。

  • 数学符号:⊕
  • 编程通用符号:^(C、C++、Java、Python等语言通用)
真值表
输入A 输入B 结果 A⊕B
0 0 0
0 1 1
1 0 1
1 1 0
本质:二进制无进位加法

异或等价于二进制逐位相加、丢弃所有进位,它和普通加法的本位计算结果完全一致,唯一区别是普通加法会向高位传递进位,而异或直接舍弃进位。

拓展:计算机底层的加法器正是由「异或(计算本位和)+ 与运算(计算进位)」组合实现,核心公式: a + b = (a \\oplus b) + ((a \& b) \<\< 1)

核心数学性质

所有算法技巧的底层根基,均可由定义直接推导得出:

  1. 交换律 :a ^ b = b ^ a
  2. 结合律 :(a ^ b) ^ c = a ^ (b ^ c)
  3. 自反性 :a ^ a = 0(任何数与自身异或,结果为0)
  4. 恒等性 :a ^ 0 = a(任何数与0异或,数值保持不变)
  5. 还原推论(对合性) :a ^ b ^ b = a 一个数连续异或同一个数两次,会还原为原始值。这是交换变量、加密解密、分组求解的核心原理。

2. 触发信号:什么时候想到用 ^

题目出现以下特征时,优先考虑异或运算:

  • 元素成对出现、找出现奇数次的数字
  • 找缺失的数字、找重复的数字
  • 求两个数二进制位的差异数量
  • 不使用临时变量交换两个整数
  • 简单加密解密、数据还原
  • 要求O(1)空间复杂度、线性时间复杂度

3. 经典应用场景

场景1:无需临时变量交换两个整数
为什么能这么做

依托异或的结合律与自反性,通过连续异或两次还原出对方的原始值,全程不需要第三个临时变量,空间复杂度O(1)。(但是这种方法存在的隐患是:当a,b是数组中的数时,要注意,a、b不能在同一个位置)

代码实现(Java)
cpp 复制代码
// 初始状态:a = 甲, b = 乙
a = a ^ b;  // 第一步:a = 甲^乙,b 仍为 乙
b = a ^ b;  // 第二步:b = (甲^乙)^乙 = 甲,b完成交换
a = a ^ b;  // 第三步:a = (甲^乙)^甲 = 乙,a完成交换
对应题目/场景
  • 经典面试题:不使用临时变量交换两个整数

场景2:查找单个出现奇数次的数字
前置条件

数组中只有1个数字出现奇数次,其余所有数字均出现偶数次。

为什么能这么做

以0为初始值,遍历数组依次异或。出现偶数次的数字会两两异或、相互抵消为0,最终剩余的结果就是唯一出现奇数次的数字。

代码实现(Java)
复制代码
public static void printOddTimesNum1(int[] arr) {
    int eor = 0;
    for (int cur : arr) {
        eor ^= cur;
    }
    System.out.println(eor);
}
对应题目
  • 力扣136. 只出现一次的数字

场景3:查找两个出现奇数次的数字
前置条件

数组中有2个不同的数字出现奇数次,其余所有数字均出现偶数次。

核心思路:分组异或

将问题拆解为两个「单个奇数次数字」的子问题:

  1. 先全员异或得到两个目标数的差值 eor = a ^ b
  2. 用 eor & (~eor + 1) 提取最右侧的1作为分组标记位(两个目标数在该位一定一个为0、一个为1)
  3. 按标记位将数组分为两组,每组分别异或,各得到一个目标数
代码实现(Java)
复制代码
public static void printOddTimesNum2(int[] arr) {
    int eor = 0;
    for (int i = 0; i < arr.length; i++) {
        eor ^= arr[i];
    }
    // 提取二进制最右侧的1,作为分组依据
    int rightOne = eor & (~eor + 1);
    
    int onlyOne = 0;
    for (int cur : arr) {
        // 只异或该位为0的一组数
        if ((cur & rightOne) == 0) {
            onlyOne ^= cur;
        }
    }
    // 输出两个出现奇数次的数字
    System.out.println(onlyOne + " " + (eor ^ onlyOne));
}

位运算技巧说明:eor & (~eor + 1) 依托整数补码规则,对原数取反加1后,原数最右侧的1会被保留,其余位全部变为0;与原数按位与后,最终仅剩下最右侧的1。

对应题目
  • 力扣260. 只出现一次的数字 III

场景4:标记二进制差异位
为什么能这么做

异或的结果天然就是两个数的「差异图」:每一个1都代表两个数在该位不同,每一个0都代表该位相同。可以直接把「找不同位的数量」转化为「统计结果中1的个数」。

代码示例
复制代码
// 得到两个数的二进制差异标记图
int getDiff(int x, int y) {
    return x ^ y;
}
对应题目
  • 力扣461. 汉明距离

场景5:简易异或加密解密
为什么能这么做

利用异或的还原推论:加密和解密是完全相同的操作。明文异或密钥得到密文,密文再异或一次相同密钥,直接还原明文。

代码示例
复制代码
// 加密解密为同一个操作
char xorCrypt(char c, int key) {
    return (char)(c ^ key);
}
对应题目/场景
  • 基础加密算法题、数据简单混淆

三、双运算符组合经典算法(进阶)

绝大多数位运算进阶题都是二者配合使用,以下三类是最经典的组合范式,明确每个运算符的分工即可快速解题。

组合1:汉明距离(力扣461)

触发信号

求两个整数二进制对应位不同的位置的数量。

分工原理
  • 异或 ^:完成「差异编码」,把所有不同的位标记为1
  • 按位与 &:完成「逐位校验」,统计结果中1的总个数
完整代码(C++)
复制代码
class Solution {
public:
    int hammingDistance(int x, int y) {
        int diff = x ^ y;
        int count = 0;
        while (diff != 0) {
            count += diff & 1;
            diff = diff >> 1;
        }
        return count;
    }
};
对应题目
  • 力扣461. 汉明距离
  • 力扣2220. 转换数字的最少位翻转次数

组合2:无加减号实现整数加法(力扣371)

触发信号

禁止使用 +、- 算术运算符,实现两个整数的加法。

分工原理
  • 异或 ^:计算无进位的本位和
  • 按位与 & + 左移:计算所有位产生的进位总值
  • 循环迭代,直到进位为0,此时本位和就是最终结果
完整代码(C++)
复制代码
class Solution {
public:
    int getSum(int a, int b) {
        while (b != 0) {
            int carry = (unsigned int)(a & b) << 1; // 计算进位
            a = a ^ b; // 计算无进位本位和
            b = carry; // 进位作为新的b继续迭代
        }
        return a;
    }
};
对应题目
  • 力扣371. 两整数之和

组合3:两个出现奇数次的数字(力扣260)

触发信号

数组中恰好有两个数字只出现一次,其余所有数字都出现两次。

分工原理
  • 异或 ^:全员异或得到两个目标数的差值 a ^ b
  • 按位与 &:提取差值最右侧的1作为分组掩码,同时用于判断每个数的分组
  • 按分组分别异或,每组各得到一个目标数
完整代码(C++)
复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int eor = 0;
        for (int num : nums) {
            eor ^= num;
        }
        // 提取最右侧的1作为分组依据
        int rightOne = eor & (~eor + 1);
        int onlyOne = 0;
        for (int num : nums) {
            if ((num & rightOne) == 0) {
                onlyOne ^= num;
            }
        }
        return {onlyOne, eor ^ onlyOne};
    }
};
对应题目
  • 力扣260. 只出现一次的数字 III

四、总结与解题方法论

1. 核心逻辑总结

所有异或的编程技巧,本质都是对「自反性 + 还原推论」的复用:

  • 单个奇数次数字:全员异或,偶数次数字全部抵消
  • 两个奇数次数字:用差异位拆分,退化为两个单次问题
  • 交换变量:连续异或两次,还原出对方原值

按位与则始终扮演「精准控制比特位」的角色:判断、提取、算进位,和异或形成互补。

这类位运算方案的统一优势:时间复杂度O(n),空间复杂度O(1),无需额外的哈希表、数组等存储结构,是算法优化中的经典手段。

2. 固定解题思考步骤

拿到一道位运算题,按以下四步走即可:

  1. 看特征匹配:看题目关键词,匹配「触发信号」,确定用&、^还是组合使用
  2. 明确分工:想清楚每个运算分别做什么:谁做抵消、谁做标记、谁做筛选
  3. 小样例推演:用小数手动计算一遍二进制,验证逻辑是否正确
  4. 编码实现:翻译成代码,注意符号位、边界条件
相关推荐
DRobot2 小时前
【机械制图与CAD实战(二十五)】螺纹的表示方法
算法·机械制图
用户老李2 小时前
卡尔曼滤波自学教程 10:无迹卡尔曼滤波(UKF)
算法
天外飞雨道沧桑2 小时前
我在浏览器里练了一个 792 参数的大模型,它学会了补全 JavaScript
开发语言·javascript·ai·ecmascript·ai编程
hhhjhl2 小时前
Yup 跨端三方库仓颉适配全流程
java·开发语言
运维行者_3 小时前
网络监控工具怎么选?从设备监控到智能运维的能力清单
运维·服务器·开发语言·网络·人工智能·支持向量机·php
深圳市益普科技有限公司3 小时前
半导体良率管理YMS:良率与设备参数的相关性,先拉哪几组数据
人工智能·python·算法
“AI国潮设计-小江”3 小时前
【AIGC实战】Python+SDXL打造“潮汕英歌舞IP”万能立牌插件,批量生成“发财元宝蛋糕”视觉营销方案(附源码)
开发语言·人工智能·python·prompt·aigc
lhbweilai4 小时前
分析FreeRTOS内核对象API的源码实现(八)
c语言·开发语言·stm32·单片机
努力努力再努力wz4 小时前
【C++高阶系列】C++20 协程底层原理:函数为什么能暂停?从状态机到 Task,彻底串起挂起、恢复与协程调度
开发语言·c++·c++20·1024程序员节