【算法题】位运算

位运算是利用二进制位特性 解决问题的高效算法,核心通过「与(&)、或(|)、异或(^)、移位(<< / >>)」等基础操作,将时间/空间复杂度优化到极致(通常为 O(n)O(n)O(n) 时间、O(1)O(1)O(1) 空间)。

它的应用场景覆盖"位计数""找唯一数""数值运算""状态压缩"等核心问题,是算法面试中高频且易掌握的考点。本文将通过10道经典题目,拆解位运算在不同场景下的解题逻辑与代码实现。

一、汉明重量(统计二进制中1的个数)

题目描述:

输入一个无符号整数(二进制串形式),返回其二进制表达式中数字位数为 1 的个数(汉明重量)。

示例

  • 输入:n = 00000000000000000000000000001011,输出:3
  • 输入:n = 11111111111111111111111111111101,输出:31

解题思路:

利用 n & (n - 1) 的核心特性:该操作会消去 n 二进制中最右边的1 。循环执行该操作直到 n 为0,统计循环次数即为1的个数。

完整代码:

cpp 复制代码
class Solution {
public:
    int hammingWeight(int n) {
        int cnt = 0;
        while(n)
        {
            n &= (n - 1);
            cnt++;
        }
        return cnt;
    }
};

复杂度分析:

  • 时间复杂度:O(k)O(k)O(k),k 是二进制中1的个数(最坏 O(32)O(32)O(32),常数级)。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

二、比特位计数

题目描述:

给定整数 n,对 0 ≤ i ≤ n 的每个 i,计算其二进制中 1 的个数,返回长度为 n+1 的结果数组。

示例

  • 输入:n = 2,输出:[0,1,1]
  • 输入:n = 5,输出:[0,1,1,2,1,2]

解题思路:

复用"汉明重量"的核心逻辑:遍历 0~n 的每个数,逐个统计二进制中1的个数,存入结果数组。

完整代码:

cpp 复制代码
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> ret;
        int cnt = 0;
        for(int i = 0; i <= n; i++)
        {
            int tmp = i;
            while(tmp)
            {
                tmp &= (tmp - 1);
                cnt++;
            }
            ret.push_back(cnt);
            cnt = 0;
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n×k)O(n×k)O(n×k),k 是单个数字二进制中1的个数(最坏 O(32n)O(32n)O(32n),仍为线性级)。
  • 空间复杂度:O(1)O(1)O(1),结果数组为必要输出,不计入额外空间。

三、汉明距离

题目描述:

两个整数的汉明距离是其二进制位不同的位置数目。给定 xy,返回它们的汉明距离。

示例

  • 输入:x = 1, y = 4,输出:2(1:0001,4:0100,不同位有2个)

解题思路:

  1. 异或操作 x ^ y:结果中 1 的位置对应两个数二进制不同的位置,0 对应相同位置。
  2. 统计异或结果中 1 的个数(复用汉明重量逻辑),即为汉明距离。

完整代码:

cpp 复制代码
class Solution {
public:
    int hammingDistance(int x, int y) {
        int n = x^y;
        int ret = 0;
        while(n != 0)
        {
            n &= n - 1;
            ret++;
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(k)O(k)O(k),k 是异或结果中1的个数(常数级)。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

四、只出现一次的数字(其余出现两次)

题目描述:

给定非空整数数组,除某个元素只出现一次外,其余元素均出现两次。找出该元素(要求线性时间、不使用额外空间)。

示例

  • 输入:nums = [2,2,1],输出:1
  • 输入:nums = [4,1,2,1,2],输出:4

解题思路:

利用异或的核心特性:

  • a ^ a = 0(相同数异或抵消);
  • a ^ 0 = a(与0异或保留自身);
  • 异或满足交换律/结合律。
    遍历数组,所有数异或的结果即为只出现一次的数。

完整代码:

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for(auto x : nums)
        {
            ret ^= x;
        }
        return ret;   
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),遍历数组一次。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

五、只出现一次的数字(其余出现三次)

题目描述:

给定整数数组,除某个元素只出现一次外,其余元素均出现三次。找出该元素(要求 O(n)O(n)O(n) 时间、O(1)O(1)O(1) 空间)。

示例

  • 输入:nums = [2,2,3,2],输出:3
  • 输入:nums = [0,1,0,1,0,1,99],输出:99

解题思路:

二进制位统计

  1. 遍历每一位(0~31位,覆盖int所有位):
    • 统计数组中所有数在该位上 1 的总个数 sum
    • sum % 3 = 1,说明唯一数在该位上是 1(其余数出现三次,该位1的个数是3的倍数,抵消后剩余1);
  2. 逐位构建结果:将为1的位通过 ret |= 1 << i 写入结果。

完整代码:

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for(int i = 0; i < 32; i++)
        {
            int sum = 0;
            for(auto x : nums)
                if(((x >> i) & 1) == 1)
                    sum++;
            sum %= 3;
            if(sum == 1) ret |= 1 << i;
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(32n)O(32n)O(32n),遍历32位,每位遍历数组一次(仍为线性级 O(n)O(n)O(n))。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

六、只出现一次的数字(两个唯一数)

题目描述:

给定整数数组,恰好两个元素只出现一次,其余元素均出现两次。找出这两个元素(要求 O(n)O(n)O(n) 时间、O(1)O(1)O(1) 空间)。

示例

  • 输入:nums = [1,2,1,3,2,5],输出:[3,5]

解题思路:

  1. 全体异或:得到两个唯一数的异或和 xor_sum(其余数出现两次,异或抵消)。
  2. 找最低位1:lowbit = xor_sum & (-xor_sum),该位表示两个唯一数在该位上二进制不同。
  3. 分组异或:按 lowbit 将数组分为两组(该位为0/1),每组内只有一个唯一数,其余数出现两次,分别异或得到结果。
    ⚠️ 注意:== 优先级高于 &,判断时需加括号。

完整代码:

cpp 复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        unsigned int xor_sum = 0;
        for(auto c : nums)
        {
            xor_sum ^= c;
        }
        int lowbit = xor_sum & (-xor_sum);

        vector<int> ret(2);
        for(auto c : nums)
        {
            if((lowbit & c) == 0) // == 优先级高于 &,必须加括号
                ret[0] ^= c;
            else 
                ret[1] ^= c;
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),两次遍历数组。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

七、判定字符是否唯一

题目描述:

实现算法判断字符串 astr 的所有字符是否唯一(进阶:不使用额外数据结构)。

示例

  • 输入:astr = "leetcode",输出:false
  • 输入:astr = "abc",输出:true

解题思路:

状态压缩:用一个 int(32位)的每一位表示对应字母(a-z)是否出现过:

  1. 若字符串长度>26,直接返回 false(a-z仅26个字母)。
  2. 遍历字符,计算偏移量 i = ch - 'a'
    • 检查 (bitMap >> i) & 1,若为1说明字符重复;
    • 否则将 bitMap 的第 i 位设为1(bitMap |= (1 << i))。

完整代码:

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;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),遍历字符串一次(n≤26,常数级)。
  • 空间复杂度:O(1)O(1)O(1),仅用一个int存储状态。

八、缺失的数字

题目描述:

给定包含 [0, n]n 个数的数组 nums,找出 [0, n] 范围内未出现的数。

示例

  • 输入:nums = [3,0,1],输出:2
  • 输入:nums = [0,1],输出:2

解题思路:

复用异或特性:

  1. 将数组所有数异或,得到中间结果。
  2. 再将中间结果异或 0~n 的所有数,最终结果即为缺失的数(出现两次的数抵消,缺失数仅异或一次)。

完整代码:

cpp 复制代码
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int ret = 0;
        for(auto ch : nums) ret ^= ch;
        for(int i = 0; i <= nums.size(); i++) ret ^= i;
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),两次遍历(数组+0~n)。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

九、两整数之和

题目描述:

不使用 +- 运算符,计算并返回两个整数 ab 的和。

示例

  • 输入:a = 1, b = 2,输出:3
  • 输入:a = -2, b = 3,输出:1

解题思路:

用位运算模拟加法:

  1. 异或 a ^ b:得到两数相加无进位的结果。
  2. 与运算后左移1位 (a & b) << 1:得到相加的进位值
  3. 循环:将无进位结果作为新的 a,进位作为新的 b,直到进位为0,此时 a 即为和。

完整代码:

cpp 复制代码
class Solution {
public:
    int getSum(int a, int b) {
        while(b)
        {
            int x = a ^ b;
            int carry = (a & b) << 1;
            a = x;
            b = carry;
        }
        return a;
    }
};

复杂度分析:

  • 时间复杂度:O(k)O(k)O(k),k 是二进制位数(常数级)。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。

十、丢失的两个数字

题目描述:

给定数组包含 1~N 的整数(缺两个数),在 O(n)O(n)O(n) 时间、O(1)O(1)O(1) 空间内找出这两个数。

示例

  • 输入:nums = [1],输出:[2,3]
  • 输入:nums = [2,3],输出:[1,4]

解题思路:

结合"两个唯一数"和"缺失数字"的逻辑:

  1. 全体异或:数组所有数异或 1~n+2n 是数组长度),得到两个缺失数的异或和 ret
  2. 找最低位1:lowbit = ret & (-ret),按该位分组。
  3. 分组异或:每组内分别异或数组元素和 1~n+2 的数,得到两个缺失数。

完整代码:

cpp 复制代码
class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) {
        int ret = 0;
        for(auto x : nums) ret ^= x;
        for(int i = 1; i <= nums.size() + 2; i++) ret ^= i;

        int lowbit = ret & (-ret);
        vector<int> ans(2);
        for(auto x : nums) 
            if((lowbit & x) == 0)
                ans[0] ^= x;
            else
                ans[1] ^= x;
        for(int i = 1; i <= nums.size() + 2; i++)
            if((lowbit & i) == 0)
                ans[0] ^= i;
            else
                ans[1] ^= i;
        return ans;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),多次遍历数组/数字范围,均为线性级。
  • 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
相关推荐
郝学胜-神的一滴2 小时前
李航《机器学习方法》全面解析与高效学习指南
人工智能·python·算法·机器学习·数学建模·scikit-learn
CS创新实验室2 小时前
奈奎斯特定理:信号处理与通信领域的基石理论
计算机网络·算法·信号处理·奈奎斯特定理
雪花desu2 小时前
【Hot100-Java简单】/LeetCode 283. 移动零:两种 Java 高效解法详解
数据结构·python·算法
随意起个昵称2 小时前
【做题总结】顺子(双指针)
c++·算法
一杯咖啡Miracle2 小时前
UV管理python环境,打包项目为docker流程
python·算法·docker·容器·uv
玉树临风ives2 小时前
atcoder ABC438 题解
数据结构·算法
算法与编程之美3 小时前
探索不同的损失函数对分类精度的影响
人工智能·算法·机器学习·分类·数据挖掘
MSTcheng.3 小时前
【C++】平衡树优化实战:如何手搓一棵查找更快的 AVL 树?
开发语言·数据结构·c++·avl
-Excalibur-3 小时前
ARP RIP OSPF BGP DHCP以及其他计算机网络当中的通信过程和广播帧单播帧的整理
c语言·网络·python·学习·tcp/ip·算法·智能路由器