力扣经典位运算

190. 颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。

示例 1:

  • 输入:n = 00000010100101000001111010011100
  • 输出:964176192(二进制表示为 00111001011110000010100101000000

示例 2:

  • 输入:n = 11111111111111111111111111111101
  • 输出:3221225471(二进制表示为 10111111111111111111111111111111

提示:

输入是一个长度为 32 的二进制字符串。

进阶:

如果多次调用这个函数,你将如何优化你的算法?

位运算逐位处理

初始化结果为 0;遍历 32 次,每次取输入值的最低位并将其放到结果的最高位,然后将输入值右移一位,结果左移一位以便下一次操作。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
uint32_t reverseBits(uint32_t n) {
    uint32_t ans = 0;
    for (int i = 0; i < 32; ++i) {
        ans = ans << 1;
        ans |= n & 1;
        n = n >> 1;
    }
    return ans;
}
int main() {
    std::cout << reverseBits(43261596) << std::endl;
    return 0;
}    

代码说明

  • 初始化 ans0,用于存储颠倒后的二进制位。
  • 遍历 32 次,每次处理一个二进制位:
    • ans 左移一位,为下一个二进制位腾出位置。
    • 使用位与操作 n & 1 取出 n 的最低位,并将其与 ans 进行位或操作,添加到 ans 的最低位。
    • n 右移一位,以便下次处理下一个二进制位。
  • 最后返回 ans,即为颠倒后的 32 位无符号整数。

时间复杂度

这种方法的时间复杂度为 O(1),因为循环次数固定为 32 次,不随输入规模变化。

191. 位1的个数

给定一个正整数 n,编写一个函数,获取该正整数的二进制形式并返回其二进制表达式中设置位(即值为 1 的位)的个数,这被称为汉明重量。

示例 1:

  • 输入:n = 11
  • 输出:3
  • 解释:输入的二进制表示为 1011,其中有 3 个设置位。

示例 2:

  • 输入:n = 128
  • 输出:1
  • 解释:输入的二进制表示为 10000000,其中有 1 个设置位。

示例 3:

  • 输入:n = 2147483645
  • 输出:30
  • 解释:输入的二进制表示为 1111111111111111111111111111101,其中有 30 个设置位。

提示

  • 1 <= n <= 2^31 - 1

进阶

如果多次调用该函数,如何优化算法?

循环移位

通过循环将数字的每一位移到最低位,然后检查该位是否为 1

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
using namespace std;

int hammingWeight(uint32_t n) {
    int count = 0;
    while(n){
        if(n&1)
            count++;
        n = n>>1;
    }
    return count;
}

int main(){
    uint32_t n = 00000000000000000000000000001011;
    cout<<hammingWeight(n)<<endl;
    return 0;
}

代码说明

  • 初始化计数器int count = 0; 用于记录设置位的个数。
  • 循环处理每一位 :在 while (n) 循环中,每次将 n 右移一位,并检查最低位是否为 1。
  • 检查最低位count += n & 1; 如果最低位是 1,则增加计数器。
  • 右移操作n >>= 1;n 右移一位,以便在下一次循环中处理下一个二进制位。
  • 返回计数器:循环结束后,返回计数器的值,即为设置位的个数。

时间复杂度

该算法的时间复杂度为 O(1),因为无论输入值的大小如何,都需要固定 32 次循环操作(对于 32 位整数)。

231. 2 的幂

给定一个整数 n,判断该整数是否是 2 的幂次方。如果是,返回 true;否则,返回 false。如果存在一个整数 x 使得 n == 2^x,则认为 n2 的幂次方。

示例 1:

  • 输入:n = 1
  • 输出:true
  • 解释:2^0 = 1

示例 2:

  • 输入:n = 16
  • 输出:true
  • 解释:2^4 = 16

示例 3:

  • 输入:n = 3
  • 输出:false

提示:

  • -2^31 <= n <= 2^31 - 1

进阶:

能否不使用循环或递归来解决这个问题?

性质

要判断一个整数是否是 2 的幂次方,可以通过检查其二进制表示是否只有一个位是 1 来实现。具体来说,2 的幂次方的二进制形式只有一个 1,其余位都是 0。例如,2^0 = 1 的二进制是 12^1 = 2 的二进制是 102^2 = 4 的二进制是 100,以此类推。

可以使用位运算来高效地解决这个问题。具体来说,可以利用以下性质:

  • 如果 n2 的幂次方,那么 n 的二进制表示中只有一个 1
  • 因此,n & (n - 1) 的结果应该为 0,因为将 n 减去 1 后,二进制表示中的 1 会变成 0,后面的 0 会变成 1

此外,还需要确保 n 是正数,因为负数和零不可能是 2 的幂次方。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
bool isPowerOfTwo(int n) {
    if (n <= 0) return false;
    return (n & (n - 1)) == 0;
}
int main() {
    std::cout << isPowerOfTwo(1) << std::endl;
    std::cout << isPowerOfTwo(2) << std::endl;
    std::cout << isPowerOfTwo(3) << std::endl;
    std::cout << isPowerOfTwo(4) << std::endl;
}

代码说明

  1. 检查正数 :首先检查 n 是否大于零。如果 n 小于或等于零,直接返回 false
  2. 位运算 :使用 n & (n - 1) 操作来检查 n 是否是 2 的幂次方。如果结果为 0,则说明 n2 的幂次方。

时间复杂度

该算法的时间复杂度为 O(1),因为它只涉及常数次的位运算。

338. 比特位的计数

给定一个整数 n,对于 0 <= i <= n 中的每个 i,计算其二进制表示中 1 的个数,返回一个长度为 n + 1 的数组 ans 作为答案。

示例 1:

  • 输入:n = 2
  • 输出:[0, 1, 1]
  • 解释:
    • 0 的二进制表示为 0,有 01
    • 1 的二进制表示为 1,有 11
    • 2 的二进制表示为 10,有 11

示例 2:

  • 输入:n = 5
  • 输出:[0, 1, 1, 2, 1, 2]
  • 解释:
    • 0 的二进制表示为 0,有 01
    • 1 的二进制表示为 1,有 11
    • 2 的二进制表示为 10,有 11
    • 3 的二进制表示为 11,有 21
    • 4 的二进制表示为 100,有 11
    • 5 的二进制表示为 101,有 21

提示

  • 0 <= n <= 10^5

进阶

能否在线性时间复杂度 O(n) 内用一趟扫描解决此问题?能否不使用任何内置函数?

动态规划

  1. 初始化数组 :创建一个数组 ans,其中 ans[i] 表示整数 i 的二进制表示中 1 的个数。
  2. 动态规划转移方程 :对于每个整数 i,其二进制中 1 的个数可以通过以下关系式计算:
    • ans[i] = ans[i >> 1] + (i & 1)
    • 其中,i >> 1 表示将 i 右移一位(相当于整数除以 2),i & 1 表示检查 i 的最低位是否为 1。
  3. 遍历计算:从 0 到 n 遍历每个整数,利用上述转移方程计算每个整数的二进制中 1 的个数。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

vector<int> countBits(int num) {
    vector<int> res(num+1, 0);
    for (int i = 1; i <= num; i++) {
        res[i] = res[i >> 1] + (i & 1);
    }
    return res;
}

int main() {
    int num = 5;
    vector<int> res = countBits(num);
    for (int i = 0; i < res.size(); i++) {
        cout << res[i] << " ";
    }
}

代码说明

总个数ans[i] 的值等于 ans[i >> 1](去掉最低位后的 1 的个数)加上 (i & 1)(最低位是否为 1)。

示例

i = 5(二进制 101)为例:

  • i >> 1 = 2(二进制 10),ans[2] 已知为 1。
  • i & 1 = 1,因为 5 的最低位是 1。
  • 因此,ans[5] = ans[2] + 1 = 1 + 1 = 2,即 5 的二进制中有两个 1

时间复杂度

该算法的时间复杂度为 O(n),因为每个整数从 0n 都只需要进行常数次操作。

461. 汉明距离

两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。

示例 1:

  • 输入:x = 1, y = 4
  • 输出:2
  • 解释:
    • 1 的二进制表示为 0 0 0 1
    • 4 的二进制表示为 0 1 0 0
    • 对应二进制位不同的位置如箭头所示。

示例 2:

  • 输入:x = 3, y = 1
  • 输出:1

提示:

  • 0 <= x, y <= 2^31 - 1

逐位比较

  • 初始化计数器int count = 0; 用于记录汉明距离,即不同位的数目。
  • 循环条件while(x != 0 || y != 0) 确保循环继续直到 x 和 y 都变为 0,这意味着所有二进制位都已被比较。
  • 比较最低位(x & 1)(y & 1) 分别获取 x 和 y 的最低位。如果这两个最低位不相同,if((x & 1) != (y & 1)) 条件成立,计数器 count 增加 1。
  • 右移操作x >>= 1y >>= 1 分别将 x 和 y 右移一位,以便在下一次循环中比较下一个二进制位。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
using namespace std;
int hammingDistance(int x, int y) {
    int count = 0;
    while(x != 0 || y != 0){
        if((x & 1) != (y & 1)){
            count++;
        }
        x >>= 1;
        y >>= 1;
    }
    return count;
}
int main(){
    cout << hammingDistance(1,4) << endl;
    return 0;
}

异或操作

  1. 异或操作 :计算 xy 的异或结果。异或操作会使得相同位为 0,不同位为 1,因此异或结果中的 1 的个数即为汉明距离。
  2. 计算 1 的个数 :统计异或结果中 1 的个数,这可以通过循环移位和按位与操作来实现。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
using namespace std;
int hammingDistance(int x, int y) {
    int xorResult = x ^ y;
    int count = 0;
    while (xorResult){
        if (xorResult & 1){
            count++;
        }
        xorResult >>= 1;
    }
    return count;
}
int main(){
    cout << hammingDistance(1,4) << endl;
    return 0;
}

代码说明

  1. 异或操作int xorResult = x ^ y; 计算 x 和 y 的异或结果。
  2. 初始化距离计数器int distance = 0; 用于记录汉明距离。
  3. 循环处理每一位
    • distance += xorResult & 1; 检查异或结果的最低位是否为 1,如果是,则增加距离计数器。
    • xorResult >>= 1; 将异或结果右移一位,以便处理下一位。
  4. 返回结果:循环结束后,返回距离计数器的值,即为汉明距离。

时间复杂度

这种方法的时间复杂度为 O(1),因为循环次数固定为 32 次(对于 32 位整数)。

136. 只出现一次的数字

给你一个非空整数数组 nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1:

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

示例 2:

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

示例 3:

  • 输入:nums = [1]
  • 输出:1

提示

  • 1 <= nums.length <= 3 * 10^4
  • -3 * 10^4 <= nums[i] <= 3 * 10^4
  • 除了某个元素只出现一次以外,其余每个元素均出现两次。

异或操作

异或运算有一个特性:一个数异或自己等于 0,一个数异或 0 等于其本身。因此,如果我们对数组中的所有元素进行异或运算,成对出现的元素都会抵消为 0,最后剩下的就是那个只出现一次的元素。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
int singleNumber(vector<int>& nums){
    int ans = 0;
    for(auto i : nums){
        ans ^= i;
    }
    return ans;
}
int main(){
    vector<int> nums = {4,1,2,1,2};
    cout << singleNumber(nums) << endl;
    return 0;
}

代码说明

  1. 初始化结果int result = 0; 用于存储异或结果。
  2. 遍历数组:对数组中的每个元素进行异或运算。
  3. 返回结果 :最终 result 中存储的就是只出现一次的元素。

时间复杂度

这种方法的时间复杂度为 O(n),空间复杂度为 O(1)

137. 只出现一次的数字 II

给你一个整数数组 nums,除某个元素仅出现一次外,其余每个元素都恰出现三次。请你找出并返回那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。

示例 1:

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

示例 2:

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

提示

  • 1 <= nums.length <= 3 * 10^4
  • -2^31 <= nums[i] <= 2^31 - 1
  • 除某个元素仅出现一次外,其余每个元素都恰出现三次。

性质

要解决这个问题,可以利用位运算的技巧来记录每个二进制位出现的次数。因为每个元素出现的次数要么是 1 次,要么是 3 次,所以可以通过统计每个二进制位上 1 出现的次数,并对 3 取模来消除出现三次的元素的影响。

  1. 统计每个二进制位上的 1 的个数 :对每个二进制位(总共 32 位),统计所有数字中该位上 1 出现的次数。
  2. 对 3 取模 :因为出现三次的元素在每个二进制位上的 1 的个数都是 3 的倍数,所以将每个二进制位的计数对 3 取模后,剩下的就是只出现一次的元素在该位上的值。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int singleNumber(vector<int> &nums)
{
    vector<int> bits(32, 0);
    for (auto num : nums)
    {
        for (int i = 0; i < 32; ++i)
        {
            bits[i] += num & 1;
            num >>= 1;
        }
    }
    int result = 0;
    for (int i = 0; i < 32; ++i)
    {
        result += bits[i] % 3 << i;
    }
    return result;
}
int main()
{
    vector<int> nums = {2, 2, 3, 2};
    cout << singleNumber(nums) << endl;
    return 0;
}

代码说明

  1. 统计每个二进制位上的 1 的个数bits 数组用于记录每个二进制位上的 1 的个数。每个元素 num 都被分解为 32 位二进制数,每一位如果是 1 则对应位置的计数加 1
  2. 对 3 取模并构造结果 :将每个二进制位上的计数对 3 取模后,如果结果为 1,则表示该位属于只出现一次的元素。通过左移和按位或操作将这些位组合成最终结果。

260. 只出现一次的数字 Ⅲ

给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。找出只出现一次的那两个元素。你可以按任意顺序返回答案。

示例 1:

  • 输入:nums = [1, 2, 1, 3, 2, 5]
  • 输出:[3, 5]
  • 解释:[5, 3] 也是有效的答案。

示例 2:

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

示例 3:

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

提示

  • 2 <= nums.length <= 3 * 10^4
  • -2^31 <= nums[i] <= 2^31 - 1
  • 除两个只出现一次的整数外,nums 中的其他数字都出现两次。

性质

  1. 异或所有元素:首先对数组中的所有元素进行异或操作。由于异或操作的性质,相同元素异或结果为 0,因此最终的异或结果实际上是两个只出现一次的元素的异或结果。
  2. 找到第一个不同的位 :找到异或结果中第一个为 1 的位。这个位表示两个只出现一次的元素在该位上不同。
  3. 分组异或 :根据上述找到的位,将数组中的元素分为两组。一组是该位为 1 的元素,另一组是该位为 0 的元素。然后分别对这两组进行异或操作,得到两个只出现一次的元素。

C++ ACM 模式

cpp 复制代码
#include <vector>
#include <iostream>
using namespace std;
vector<int> singleNumber(vector<int>& nums) {
    int xor_all = 0;
    for (int num : nums) {
        xor_all ^= num;
    }

    int mask = 1;
    while ((xor_all & mask) == 0) {
        mask <<= 1;
    }

    int a = 0, b = 0;
    for (int num : nums) {
        if (num & mask) {
            a ^= num;
        } else {
            b ^= num;
        }
    }

    return {a, b};
}

int main() {
    vector<int> nums = {1, 2, 1, 3, 2, 5};
    vector<int> res = singleNumber(nums);
    for (auto num : res) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}

代码说明

1. 初始化 mask 为 1
mask 用于标记两个只出现一次的元素在二进制表示中第一个不同的位。

2. 找到第一个不同的位

cpp 复制代码
while ((xor_all & mask) == 0) {
    mask <<= 1;
}
  • xor_all 是所有元素异或的结果,它实际上是两个只出现一次的元素的异或结果。由于异或的性质,相同元素异或后结果为 0,所以 xor_all 保留了两个只出现一次元素的异或信息。
  • (xor_all & mask) == 0 检查 xor_all 的最低位是否为 0。如果是 0,说明两个元素在该位上的值相同,需要继续左移 mask 查找下一个位。
  • mask <<= 1mask 左移一位,直到找到 xor_all 中值为 1 的位。这个位就是两个只出现一次元素的第一个不同位。

3. 分组异或

cpp 复制代码
int a = 0, b = 0;
for (int num : nums) {
    if (num & mask) {
        a ^= num;
    } else {
        b ^= num;
    }
}
  • 根据 mask 将数组中的元素分为两组:
    • num & mask 为真的元素属于一组,这些元素在 mask 所示的位上为 1
    • 其他元素属于另一组,这些元素在 mask 所示的位上为 0
  • a ^= num 对第一组元素进行异或操作。由于成对出现的元素会抵消为 0,最终 a 保留了其中一个只出现一次的元素。
  • b ^= num 对第二组元素进行异或操作,同理,b 保留了另一个只出现一次的元素。

4. 返回结果

最终,ab 分别是两个只出现一次的元素。

371. 两整数之和

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

示例 1:

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

示例 2:

  • 输入:a = 2, b = 3
  • 输出:5

提示

  • -1000 <= a, b <= 1000

性质

  1. 异或运算 :异或运算可以模拟不考虑进位的加法。例如,a ^ b 得到的结果是不考虑进位的和。
  2. 与运算和左移:与运算可以找出需要进位的位置,然后左移一位得到进位值。
  3. 循环处理进位:不断重复上述过程,直到进位值为零。此时的结果即为两整数之和。

C++ ACM 模式实现

cpp 复制代码
#include <iostream>
using namespace std;

int getSum(int a, int b) {
    while(b != 0) {
        int carry = (unsigned int)(a & b) << 1;
        a = a ^ b;
        b = carry;
    }
    return a;
}

int main() {
    cout << getSum(1, 2) << endl;
    return 0;
}

代码说明

a = 1b = 2 为例。

初始化:

  • a = 1(二进制 01
  • b = 2(二进制 10

第一次循环:

  1. 计算进位

    • carry = a & bcarry = 1 & 2
    • 二进制表示:01 & 10 = 00carry = 0
  2. 计算不考虑进位的和

    • a = a ^ ba = 1 ^ 2
    • 二进制表示:01 ^ 10 = 11a = 3
  3. 进位左移一位

    • b = (unsigned int)carry << 1b = 0 << 1 = 0

循环条件检查:

  • b != 00 != 0 → 循环结束

在这个例子中,ab 的二进制没有需要进位的地方,所以循环只执行了一次。最终,a 的值为 3,即 1 + 2 = 3

这个过程展示了如何使用位运算来模拟加法操作。异或运算 (^) 用于计算不考虑进位的和,而与运算 (&) 和左移运算用于处理进位。通过不断重复这个过程,直到没有进位为止,最终得到正确的结果。

401. 二进制手表

二进制手表顶部有 4LED 代表小时(0-11),底部的 6LED 代表分钟(0-59)。每个 LED 代表一个 01,最低位在右侧。

给你一个整数 turnedOn,表示当前亮着的 LED 的数量,返回二进制手表可以表示的所有可能时间。你可以按任意顺序返回答案。

小时不会以零开头:

  • 例如,"01:00" 是无效的时间,正确的写法应该是 "1:00"。

分钟必须由两位数组成,可能会以零开头:

  • 例如,"10:2" 是无效的时间,正确的写法应该是 "10:02"。

示例 1:

  • 输入:turnedOn = 1
  • 输出:["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]

示例 2:

  • 输入:turnedOn = 9
  • 输出:[]

提示

  • 0 <= turnedOn <= 10

内置函数

  1. 枚举所有可能的时间组合 :小时范围是 0-11,分钟范围是 0-59
  2. 计算每个时间对应的二进制中 1 的个数 :对于每个时间,计算小时和分钟部分二进制表示中 1 的总数。
  3. 筛选符合条件的时间 :如果总数等于 turnedOn,则将该时间添加到结果列表中。
  4. 格式化输出:确保小时不以零开头,分钟必须由两位数组成。

c++ ACM 模式实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
using namespace std;

vector<string> readBinaryWatch(int num) {
    vector<string> res;
    for (int h = 0; h < 12; ++h) {
        for (int m = 0; m < 60; ++m) {
            if (__builtin_popcount(h) + __builtin_popcount(m) == num) {
                res.push_back(to_string(h) + (m < 10 ? ":0" : ":") + to_string(m));
            }
        }
    }
    return res;
}
int main() {
    vector<string> res = readBinaryWatch(1);
    for (auto &s : res) {
        cout << s << endl;
    }
    return 0;
}

代码说明

__builtin_popcountGCC(GNU Compiler Collection)提供的一款内置函数,用于计算一个无符号整数的二进制表示中有多少个 1。它的作用是快速统计二进制数中 1 的个数,这在位运算相关的编程问题中非常有用。

cpp 复制代码
int __builtin_popcount(unsigned int x);
  • x:一个无符号整数,函数将计算其二进制表示中 1 的个数。

函数返回 x 的二进制表示中 1 的个数。

推荐一下

https://github.com/0voice

相关推荐
凯子坚持 c10 分钟前
深度解析算法之模拟
算法
小比卡丘11 分钟前
【C++初阶】第15课—模版进阶
android·java·c++
GOTXX1 小时前
掌握MySQL:基本查询指令与技巧
数据库·c++·mysql·全文检索·多线程·热榜
IceTeapoy4 小时前
【RL】强化学习入门(二):Q-Learning算法
人工智能·算法·强化学习
"_rainbow_"6 小时前
C++常用函数合集
开发语言·c++·算法
SuperCandyXu7 小时前
leetcode0145. 二叉树的后序遍历-easy
算法·深度优先
roboko_8 小时前
多路转接poll服务器
linux·网络·c++
蒲公英的孩子8 小时前
Linux下 REEF3D及DIVEMesh 源码编译安装及使用
linux·c++·分布式·开源软件
半青年8 小时前
数据结构之哈希表的原理和应用:从理论到实践的全面解析
java·c语言·数据结构·c++·python·哈希算法