我们知道,一般基本位运算分为以下几种:
当我们理解了基础位运算,我们要确保可以解决下图红字中的基本问题:
通过上图设计的题目和思路,我们可以顺势解决以下几道题:
思路:
如图,我们只需要 将n的每一位都&1 即可:
代码:
int hammingWeight(uint32_t n) {
int ret = 0; // 记录结果
while (n)
{
ret += n & 1;
n >>= 1;
}
return ret;
}
思路:
题目要求计算一个(1~n)范围内每个元素,二进制中1的个数 :
代码:
vector<int> countBits(int n) {
vector<int> ret(n+1, 0);
for(int i = 0; i <= n; ++i) // 统计0 ~ n中每个元素二进制位一的个数
{
int count = 0;
int tmp = i;
while(tmp) // 计算tmp中1的个数
{
count += tmp & 1;
tmp >>= 1;
}
// 存储其1的个数
ret[i] = count;
}
return ret;
}
思路:
如图所示,我们知道异或相当于(相同为0,相异为1),只需要统计两数异或之后的结果中1的个数即就是不同位的个数,即汉明距离。
代码:
int hammingDistance(int x, int y) {
int ret = 0;
int tmp = x ^ y;
// 异或后 1的个数 即为 二进制位不同位置数
while(tmp)
{
ret += tmp & 1;
tmp >>= 1;
}
return ret;
}
思路:
题目要求找到整数数组中只出现过一次的数字,此题可以用哈希表解题,但位运算的时间空间复杂度总体更为优秀。
代码:
int singleNumber(vector<int>& nums) {
int ret = 0;
// 将全部元素异或,结果为只出现一次的数字
for(int i : nums){
ret ^= i;
}
return ret;
}
思路:
代码:
vector<int> singleNumber(vector<int>& nums) {
int tmp = 0;
for(int num : nums) tmp ^= num; // 全部元素异或
// 防止溢出
int differ = (tmp == INT_MIN ? tmp : tmp & (-tmp)); // 找a,b的不同位
int a = 0, b = 0;
for(int num : nums)
{ // 根据条件划分
if((num & differ) != 0)
a ^= num;
else
b ^= num;
}
return {a, b};
}
上面的题是小试牛刀,下面的题正式进行位运算算法的代码编写。
思路:
上题解法利用位图思想 :我们通过将字符串的每个元素存到int型变量bitMap
中(该位为0:未出现过,为1:出现过),通过判断所有位是否有1则可判断字符串的字符是否唯一。
代码:
bool isUnique(string astr) {
// 位图
if(astr.size() > 26) return false;
int bitMap = 0; // 从后向前存放字母的出现次数,0代表未出现过,1代表出现过
for(char ch : astr)
{
int i = ch - 'a'; // 取该字符位
if((bitMap >> i) & 1 == 1) return false; // 如果这一位存在,false
bitMap |= (1 << i);
}
return true;
}
题意很清晰:即不使用±运算符计算两个整数的和。
思路:
我们知道:
代码:
int getSum(int a, int b) {
while(b != 0)
{
int nsum = a ^ b; // 不进位相加结果
int carry = (a & b) << 1; // 进位数
a = nsum;
b = carry; // 重复步骤直到b==0
}
return a;
}
思路:
定义一个vector<int> count(32)
,用于存储nums中所有元素的二进制表示的各个位的出现次数;
对于记录完毕的count,count的每一位都有如下四种情况:
将得出的结果还原到ret中(用|=还原)
代码:
int singleNumber(vector<int>& nums) {
vector<int> count(32); // 存放数组所有元素的位
for(int num : nums)
for(int i = 0; i < 32; ++i) // 存
{
count[i] += (num >> i) & 1;
}
// 遍历count中的32个元素,还原只出现一次的元素到ret中
int ret = 0;
for(int i = 0; i < 32; ++i)
{
ret |= (count[i] % 3) << i;
}
return ret;
}
思路:
我们知道:a ^ a = 0 且 0 ^ b = b ,则利用这个性质,将nums中的数与0~n的所有数异或,最终结果则为消失的数字。
代码:
int missingNumber(vector<int>& nums) {
// 位运算
int n = nums.size();
int miss = 0;
for(int i = 0; i < n; ++i) // 与数组所有元素and 0~n-1的元素异或
{
miss ^= i ^ nums[i];
}
miss ^= n; // 异或n
return miss;
}
思路:
“只出现一次的数字Ⅲ” 中有个思想就是通过两元素的不同位,进行划分,这里也是一样
代码:
vector<int> missingTwo(vector<int>& nums) {
int tmp = 0;
int n = nums.size() + 2; // 数组中缺失两个数字,所以数组长度为n+2
// 1. 异或所有数
for (int i = 1; i <= n; ++i)
tmp ^= i;
for (int num : nums)
tmp ^= num;
// 2. 获取最右边为1的位数(不同位)
int differ = tmp & -tmp;
// 3. 根据differ,划分数组元素进行异或
int a = 0, b = 0;
for (int i = 1; i <= n; ++i) {
if (i & differ)
a ^= i;
else
b ^= i;
}
for (int num : nums) {
if (num & differ)
a ^= num;
else
b ^= num;
}
return {a, b};
}