欢迎光临
我们一直在努力

位运算技巧详解:从基础到实战全攻略

位运算技巧详解:从基础到实战全攻略

前言

位运算是程序设计中一种非常重要的优化技巧,它直接操作二进制位,执行效率极高。本文将系统介绍位运算的常见技巧和经典应用,包括位运算基础、位图实现以及用位运算实现加减乘除等高级技巧。


一、位运算基础技巧

1.1 判断是否是2的幂

核心思想:2的幂在二进制中只会有一位上为1,其余位上均为0。因此提取最右侧的1后如果与原数字相等则为2的幂。

LeetCode题目:Power of Two

class Solution {
public:
bool isPowerOfTwo(int n) {
return n > 0 && n == (n & (n));
}
};

复杂度分析:

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

1.2 判断是否为3的幂

核心思想:在int范围内,最大的3的幂是1162261467。如果n是3的幂,那么1162261467一定能被n整除。

LeetCode题目:Power of Three

class Solution {
public:
bool isPowerOfThree(int n) {
// 1162261467是int类型中最大的3的幂
return (n > 0 && 1162261467 % n == 0);
}
};

复杂度分析:

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

1.3 返回大于等于n的最小的2的幂

核心思想:通过一系列的位或运算,将最高位的1之后的所有位都变成1,然后加1即可得到下一个2的幂。

int near2Power(int n) {
if (n <= 0) {
return 1;
}
n; // 当n为2的幂时要减1,不然不会返回其本身而是返回比它大的2的幂

n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return n + 1;
}

算法解释:

举例:n为 00101100,n–为 00100011
n右移1位为 00010001
与n或 00110011
n右移2位或n为 00111111

将最左的1之后的位都变为1,再加1即为所求

复杂度分析:

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

1.4 区间按位与(Brian Kernighan算法)

问题描述:给定区间 [left, right],返回此区间内所有数字按位与的结果。

LeetCode题目:Bitwise AND of Numbers Range

核心算法:「Brian Kernighan 算法」,用于清除二进制串中最右边的1。

算法原理:

  • Brian Kernighan算法的关键在于每次对 number 和 number-1 之间进行按位与运算后,number 中最右边的1会被抹去变成0
  • 对于给定的范围 [m, n](m < n),我们对数字n迭代地应用上述技巧,清除最右边的1,直到它小于或等于m
  • 此时非公共前缀部分的1均被消去,最后返回n即可

class Solution {
public:
int rangeBitwiseAnd(int m, int n) {
while (m < n) {
// 抹去最右边的1
n = n & (n 1);
}
return n;
}
};

复杂度分析:

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

1.5 逆序二进制

问题描述:颠倒给定的32位无符号整数的二进制位。

LeetCode题目:Reverse Bits

核心思想:分而治之,把数字分为两半,然后交换这两半的顺序;再把前后两个半段都分成两半,交换内部顺序……直至最后交换的数字只有1位。

class Solution {
public:
uint32_t reverseBits(uint32_t n) {
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);
n = (n >> 16) | (n << 16);
return n;
}
};

复杂度分析:

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

1.6 统计二进制中1的个数(汉明距离)

问题描述:计算两个整数之间的汉明距离(对应二进制位不同的位置的数目)。

LeetCode题目:Hamming Distance

方法一:分组统计法

算法原理:

  • 将32位数字每两位分组统计1的个数
  • 再将每四位分组统计
  • 以此类推,最后得到32位中1的总数
  • 举例说明:

    • 八位数:11111010
    • 与01010101相与后为 01010000(统计奇数位的1)
    • 原数右移1位(01111101)再与01010101相与为 01010101(统计偶数位的1)
    • 两个结果相加为 10 10 01 01(每两位表示对应位置1的个数)

    class Solution {
    public:
    int hammingDistance(int x, int y) {
    return cntOnes(x ^ y);
    }

    int cntOnes(int n) {
    n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
    n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
    n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);
    n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);
    n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);
    return n;
    }
    };

    方法二:Brian Kernighan算法

    class Solution {
    public:
    int hammingDistance(int x, int y) {
    int n = x ^ y;
    int ret = 0;
    while (n) {
    n &= n 1; // 消除最右边的1
    ret++;
    }
    return ret;
    }
    };

    方法三:系统内置函数

    class Solution {
    public:
    int hammingDistance(int x, int y) {
    return __builtin_popcount(x ^ y);
    }
    };

    复杂度分析:

    • 时间复杂度:O(1) 或 O(k),k为1的个数
    • 空间复杂度:O(1)

    二、位图的设计与实现

    2.1 除法向上取整技巧

    对于a和b都是非负数的情况:

    // 除法向上取整
    (a + b 1) / b;


    2.2 位图的基本实现

    位图(BitMap)是一种用于快速查找、判断数据是否存在的数据结构,每个bit位代表一个数字。

    class bitMap {
    public:
    bitMap(int n) {
    bit.resize((n + 31) / 32, 0);
    }

    void add(int n) {
    bit[n / 32] |= 1 << (n % 32);
    }

    void remove(int n) {
    bit[n / 32] &= ~(1 << (n % 32));
    }

    void reverse(int n) {
    bit[n / 32] ^= 1 << (n % 32);
    }

    bool contain(int n) {
    return (((bit[n / 32] >> (n % 32)) & 1) == 1);
    }

    private:
    vector<int> bit; // 存储位
    };

    设计要点:

    • 使用整型数组存储,每个int存储32个bit
    • n/32 确定在哪个int中
    • n%32 确定在该int的哪一位

    2.3 实现Bitset类(LeetCode高级题)

    问题描述:设计一个Bitset类,支持以下操作:

    • Bitset(int size):初始化size个位,全部为0
    • void fix(int idx):将idx位置设为1
    • void unfix(int idx):将idx位置设为0
    • void flip():翻转所有位
    • boolean all():检查是否全为1
    • boolean one():检查是否至少有一个1
    • int count():返回值为1的位数
    • String toString():返回当前组成情况

    LeetCode题目:Design Bitset

    核心优化:使用反转标志位避免真实翻转所有bit,实现O(1)的flip操作。

    class Bitset {
    private:
    vector<int> set;
    bool reverse;
    int ones;
    int zores;
    int size;

    public:
    Bitset(int size) {
    set.resize((size + 31) / 32, 0);
    reverse = false;
    ones = 0;
    zores = size;
    this->size = size;
    }

    void fix(int idx) {
    int index = idx / 32;
    int bit = idx % 32;
    if (!reverse) {
    if (((set[index]) & (1 << bit)) == 0) {
    set[index] |= (1 << bit);
    zores;
    ones++;
    }
    } else {
    if (((set[index]) & (1 << bit)) != 0) {
    set[index] ^= (1 << bit);
    zores;
    ones++;
    }
    }
    }

    void unfix(int idx) {
    int index = idx / 32;
    int bit = idx % 32;
    if (!reverse) {
    if (((set[index] & (1 << bit))) != 0) {
    set[index] ^= (1 << bit);
    zores++;
    ones;
    }
    } else {
    if ((set[index] & (1 << bit)) == 0) {
    set[index] |= (1 << bit);
    zores++;
    ones;
    }
    }
    }

    void flip() {
    reverse = !reverse;
    int temp = zores;
    zores = ones;
    ones = temp;
    }

    bool all() {
    return size == ones;
    }

    bool one() {
    return ones > 0;
    }

    int count() {
    return ones;
    }

    string toString() {
    int indexs = (size + 31) / 32;
    string str = "";
    for (int i = 0; i < indexs; i++) {
    int number = set[i];
    for (int j = 0; j < 32 && (i * 32 + j) < size; j++) {
    int status = (number >> j) & 1;
    status ^= reverse ? 1 : 0;
    str.append(to_string(status));
    }
    }
    return str;
    }
    };

    复杂度分析:

    • fix/unfix/flip/all/one/count:O(1)
    • toString:O(size)

    三、位运算实现四则运算

    3.1 位运算实现加法

    核心思想:

    • a ^ b 得到不含进位的和
    • (a & b) << 1 得到进位
    • 重复上述过程直到没有进位

    int add(int a, int b) {
    while (b != 0) {
    int ans = a ^ b;
    b = (a & b) << 1;
    a = ans;
    }
    return a;
    }


    3.2 位运算实现减法

    核心思想:减法等价于加上一个数的相反数,相反数 = ~b + 1

    int sub(int a, int b) {
    return add(a, add(~b, 1));
    }


    3.3 位运算实现乘法

    核心思想:a不断左移,b右移;当b的最右位为1时,将当前的a累加到结果中。

    int mul(int a, int b) {
    int ans = 0;
    while (b != 0) {
    if (b & 1) {
    ans = add(ans, a);
    }
    a <<= 1;
    b >>= 1;
    }
    return ans;
    }


    3.4 位运算实现除法

    核心思想:不断尝试a是否大于等于b×2^n,大于则做差并在结果的对应位设置1。为避免溢出,改为a右移n位与b比较。

    LeetCode题目:Divide Two Integers

    class BitCalculator {
    public:
    int add(int a, int b) {
    while (b != 0) {
    int ans = a ^ b;
    b = (a & b) << 1;
    a = ans;
    }
    return a;
    }

    int sub(int a, int b) {
    return add(a, add(~b, 1));
    }

    int mul(int a, int b) {
    int ans = 0;
    while (b != 0) {
    if (b & 1) {
    ans = add(ans, a);
    }
    a <<= 1;
    b >>= 1;
    }
    return ans;
    }

    int neg(int a) {
    return add(~a, 1);
    }

    int div(int a, int b) {
    int x = a < 0 ? neg(a) : a;
    int y = b < 0 ? neg(b) : b;
    int ans = 0;
    for (int i = 30; i >= 0; i = sub(i, 1)) {
    if ((x >> i) >= y) {
    ans |= (1 << i);
    x = sub(x, y << i);
    }
    }
    return ((a > 0) ^ (b > 0)) ? neg(ans) : ans;
    }

    // 处理INT_MIN特殊情况
    int minDiv(int a, int b) {
    if (a == INT_MIN && b == INT_MIN) {
    return 1;
    }
    if (a != INT_MIN && b != INT_MIN) {
    return div(a, b);
    }
    if (b == INT_MIN) {
    return 0;
    }
    // a==INT_MIN b==-1时返回INT_MAX
    if (b == neg(1)) {
    return INT_MAX;
    }
    // a==INT_MIN b!=-1
    a = add(a, b > 0 ? b : neg(b));
    int ans = div(a, b);
    int offset = b > 0 ? neg(1) : 1;
    return add(ans, offset);
    }
    };

    边界处理:

    • INT_MIN的特殊处理
    • 除数为-1的溢出处理

    总结

    本文系统介绍了位运算的三大应用场景:

  • 基础位运算技巧:

    • 判断2的幂/3的幂
    • 计算下一个2的幂
    • Brian Kernighan算法(清除最右1)
    • 二进制逆序
    • 统计1的个数
  • 位图数据结构:

    • 基本位图实现
    • Bitset设计(支持O(1)翻转)
    • 空间优化技巧
  • 位运算实现算术运算:

    • 加减乘除的完整实现
    • 特殊边界情况处理
  • 位运算的优势:

    • 运算速度快(直接操作二进制)
    • 节省空间(位图)
    • 代码简洁(一行解决问题)

    学习建议:

    • 理解每种位运算的本质含义
    • 掌握常见位运算技巧的模板
    • 多做相关LeetCode题目巩固

    希望本文能帮助你掌握位运算这一重要技能!


    参考资料:

    • LeetCode位运算题集
    • 算法可视化演示

    关键字:位运算、Brian Kernighan算法、位图、Bitset、汉明距离、LeetCode


    如果本文对你有帮助,欢迎点赞收藏!有任何问题欢迎在评论区讨论交流。

    赞(0)
    未经允许不得转载:171主机测评 » 位运算技巧详解:从基础到实战全攻略
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址