目录
1.知识回顾
2.总结
基础位运算
>> << & | ! ~运算符的用法参见14.【C语言】初识操作符 上和15.【C语言】初识操作符 下文章
位运算的优先级
不记优先级,直接加括号
第i位是0还是1
设二进制数x的最低位下标为0,必须先将第i位右移到最右侧,再做处理
第i位为 (x>>i) & 1
修改第i位为1
x = (1<<i) | x; //即x |= 1<<i;
修改第i位为0
x = (~(1<<i)) & x; //即x &= ~(1<<i);
位图的思想
这里简要提一提,位图(bitmap)用比特位存储信息,其本质是哈希表
例如int类型变量有4个字节,一共32个比特位,每一个位有0和1两种状态,可以存储信息
上述提到的三种方法做题会频繁用到,可以查看和修改位图
提取最右侧的1(lowbit)
n & (-n)
-n是按位取反再+1,n&(-n)的本质是将最右边的1左边的区域全部变成相反数
将最右侧的1变成0
n & (n-1)
n & (n-1)的本质是将最右边的1及其右边的区域全部变成相反数,因为n-1是要借位的
异或操作运算律
参见E23.【C语言】练习:不创建第三个变量实现两个整数的交换文章
归零定律:a XOR a == 0
恒等定律:a XOR 0 == a
交换定律:a XOR b == b XOR a
自反定律:a XOR b XOR a == b
结合定律:a XOR b XOR c == a XOR (b XOR c) == (a XOR b) XOR c
3.练习
比特位计数
https://leetcode.cn/problems/counting-bits/description/
给你一个整数
n
,对于0 <= i <= n
中的每个i
,计算其二进制表示中1
的个数 ,返回一个长度为n + 1
的数组ans
作为答案。示例 1:
输入:n = 2 输出:[0,1,1] 解释: 0 --> 0 1 --> 1 2 --> 10示例 2:
输入:n = 5 输出:[0,1,1,2,1,2] 解释: 0 --> 0 1 --> 1 2 --> 10 3 --> 11 4 --> 100 5 --> 101提示:
0 <= n <= 10^5
进阶:
- 很容易就能实现时间复杂度为
O(n log n)
的解决方案,你可以在线性时间复杂度O(n)
内用一趟扫描解决此问题吗?- 你能不使用任何内置函数解决此问题吗?(如,C++ 中的
__builtin_popcount
)
方法1:每个数都循环32次
由于n在int范围内,因此对于0~n的所有数,使用第i位是0还是1的方法,int一共32比特,因此循环32次,时间复杂度为
class Solution {
public:
vector<int> countBits(int n)
{
vector<int> ret;
for (int i=0;i<=n;i++)
{
//int 32bit
int tmp=0;
for (int j=0;j<32;j++)
{
tmp+=(i>>j)&1;
}
ret.push_back(tmp);
}
return ret;
}
};
提交结果:
方法2:相邻奇偶的1的个数规律
从LeetCode的提示看:
相邻的奇数和偶数的1的个数其实是有规律的,设n(x)代表十进制x的二进制表示中1的个数,那么有
n(0)+1==n(1)
n(2)+1==n(3)
n(4)+1==n(5)
n(6)+1==n(7)
......
显然对于相邻的奇数和偶数,奇数的1的个数+1==偶数的1的个数,因此方法1可以优化
class Solution {
public:
vector<int> countBits(int n)
{
vector<int> ret;
for (int i = 0; i <= n; i += 2)
{
int tmp = 0;
for (int j = 0; j < 32; j++)
{
tmp += (i >> j) & 1;
}
ret.push_back(tmp);
ret.push_back(tmp + 1);
}
if (n % 2 == 0)
ret.pop_back();
return ret;
}
};
提交结果:
方法3:统计将最右侧的1变成0的次数(快速)
对于数x,每次将其最右侧的1变成0,直到x为0就停止操作,显然此方法比方法1快,不用每个数都循环32次
class Solution {
public:
vector<int> countBits(int n)
{
vector<int> ret;
for (int i=0; i<=n; i++)
{
int tmp=i;
int num=0;
while(tmp)
{
tmp&=tmp-1;
num++;
}
ret.push_back(num);
}
return ret;
}
};
当然方法3可以改进方法2,这里省略不写
提交结果:
方法4:分范围讨论(暂时不讲)
从LeetCode的提示看:
尝试分成[2-3],[4-7],[8-15]这几段看看规律
{1,2}、{1,2,2,3}、{1,2,2,3,2,3,3,4}、......有规律
后面的区间依靠前面的区间
这个解法需要用到动态规划,后面再说
汉明距离
https://leetcode.cn/problems/hamming-distance/description/
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数
x
和y
,计算并返回它们之间的汉明距离。示例 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 <= 231 - 1
注意:本题与 2220. 转换数字的最少位翻转次数 相同。
分析
先将x和y异或得到tmp,之后,使用将最右侧的1变成0的方法来统计tmp中1的个数sum,sum的值就是正确答案
class Solution {
public:
int hammingDistance(int x, int y)
{
int tmp=x^y;
int sum=0;
while (tmp)
{
tmp&=tmp-1;
sum++;
}
return sum;
}
};
提交结果: