【Leetcode】[190] 颠倒二进制位
Author: Xin Pan
Date: 2022.3.13
题目
颠倒给定的 32 位无符号整数的二进制位。
解法
考虑使用位运算来做,因为这32位0 1都很明显了。
示例 1:
输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
答案1 位移动+分治
怎么样,看到这个解法惊不惊喜意不意外。说真的,根本没想到分治。看了题解发现用了分治之后效果还是挺明显的。官方分治的题解是如下。
执行用时: 0 ms 内存消耗: 5.7 MB
class Solution
{
private:
const uint32_t M1 = 0x55555555; // 01010101010101010101010101010101
const uint32_t M2 = 0x33333333; // 00110011001100110011001100110011
const uint32_t M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
const uint32_t M8 = 0x00ff00ff; // 00000000111111110000000011111111
public:
uint32_t reverseBits(uint32_t n)
{
n = n >> 1 & M1 | (n & M1) << 1; // 奇偶交换
n = n >> 2 & M2 | (n & M2) << 2; // 每2位交换
n = n >> 4 & M4 | (n & M4) << 4; // 每4位交换
n = n >> 8 & M8 | (n & M8) << 8; // 每8位交换
return n >> 16 | n << 16;
}
};
我用代码解释下其中的每行的含义。因为位的运算比较抽象,但是效率高。
#include <iostream>
#include <bitset>
using namespace std;
int main()
{
uint32_t M1 = 0x55555555;
uint32_t n = 43261596;
// 运算符优先级 << >> 高于 按位与(&) 高于 按位异或(^) 高于 按位或(|)
// 我自己记不住 按位与(&) 按位异或(^) 按位或(|) 三个运算什么时候结果是0,什么时候结果是1。为此我变了一个顺口溜
// &:全1才是1;
// |: 有1就是1;
// ^: 不同才是1。
// 记住顺口溜我们看下边的代码
cout << "输入的二进制表示\t\t" << bitset<sizeof(n) * 8>(n) << endl;//int占4字节,一个字节8位,最终输出的是32个0或1
cout << "输入>>1\t\t\t\t" << bitset<sizeof(n) * 8>(n >> 1) << endl;//int占4字节,一个字节8位,最终输出的是32个0或1
cout << "M1(奇偶掩膜)\t\t\t" << bitset<sizeof(M1) * 8>(M1) << endl;
cout << "输入>>1&M1\t\t\t" << bitset<sizeof(n) * 8>(n >> 1 & M1) << endl;
cout << "输入&M1再<<1\t\t\t" << bitset<sizeof(n) * 8>((n & M1) << 1) << endl;
cout << "输入>>1&M1 | 输入&M1再<<1\t" << bitset<sizeof(n) * 8>(n >> 1 & M1 | (n&M1) << 1) << endl << endl;
cout << "输入的二进制表示\t\t" << bitset<sizeof(n) * 8>(n << 1) << endl;
cout << "M1(奇偶掩膜)\t\t\t" << bitset<sizeof(M1) * 8>(M1) << endl;
cout << "输入&M1\t\t\t\t" << bitset<sizeof(n) * 8>(n & M1) << endl;
cout << "输入&M1<<1\t\t\t" << bitset<sizeof(n) * 8>((n & M1) << 1) << endl;
cout << "输入<<1再&M1\t\t\t" << bitset<sizeof(n) * 8>(n << 1 & M1) << endl;
system("pause");
return 0;
}
结果如下:
- &M1这个掩膜相当于固定提取当前奇数位置的值;
那么对于上半部分来说。首先将偶数位置移动到奇数位置,在和M1取按位与计算,就是将输入的偶数信息拿过来了。为什么右移一位不能说是奇数位到偶数位。因为第一位和最后一位会在移动(左移或者右移)过程中直接被抹掉。所以原值等于变了。故而我们不说奇数变到了偶数位置。
那么怎么将原先奇数位置转移到偶数位置呢,就是直接做按位与M1,再左移就可以了。现在我们有了各自移动好位置的奇数和偶数部分,下边就是将它们合并起来,那怎么做?直接做按位或就可以了。
这就完成了奇偶位数的互换工作了。因为采用分支算法,也就是将32位对半分,直到最小的维度相邻的两个值。
反过来我们计算了相邻(奇偶的互换),同理计算每2 4 8 16互换就完成任务了。
答案2 位移动
直接解法,一位位移动
执行用时: 4 ms 内存消耗: 5.9 MB
class Solution
{
public:
uint32_t reverseBits(uint32_t n)
{
uint32_t nret = 0;
for (int i = 0; n > 0, i < 32; i++)
{
nret |= (n & 1) << (31 - i);
n >>= 1;
}
return nret;
}
};