位运算
文章目录
前言
位运算是算法题里比较特殊的一种类型,它们利用二进制位运算的特性进行一些奇妙的优化和计算。
一、算法概念
位运算总共分为两大类,逻辑位运算符和位移运算符。常用的位运算符号包括:“∧”按位异或、“&”按位与、“|”按位或、“∼”取反、“<<”算术左移和“>>”算术右移。
“∧”按位异或运算,首先将十进制的数换算成为二进制数,然后从低到高按位运算,对于每个位而言,只有当两个位不同的时候结果才为1,否则为0,位运算完成后,再把二进制转换为十进制得到运算结果。“∧”的性质,任何数和自己进行“∧”操作为0,任何数和0进行“∧”操作为它本身。
“<<”算术左移,是一种二元运算符,X<<y表示X左移y位,就是将X转换为二进制,然后将所有位左移y位,末尾y位补0。
“>>”算术右移,是一种二元运算符,X>>y表示X右移y位,与左移不同的是,如果X为非负数的高位补0,X为负数则在高位补1,因此右移操作可以看成对X除2并向下取整。
二、位运算基础问题分析和解答
1.位1的个数
问题描述
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
输入输出样例
输入:00000000000000000000000000001011
输出:3
解题思路与代码
对于二进制数10000减1为01111,对这两个数求&结果为0,因此可以用这种方式把每一位上的1都求出来。
class Solution {
public:
int hammingWeight(uint32_t n) {
int count=0;
while(n)
{
n=n&(n-1);
count++;
}
return count;
}
}
2.4的幂
问题描述
给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4^x
输入输出样例
输入:n = 16
输出:true
解题思路与代码
此题要考虑4 的幂次方数的特征,首先4 的幂次方数一定为2的幂次方数,其次4 的幂次方数减去1一定为3的倍数。对于2的幂次方数(比如:二进制数10000),其一定大于0,并且和它减一后的数做&运算结果一定为0。
class Solution {
public:
bool isPowerOfFour(int n) {
return n>0&&(n&(n-1))==0&&(n-1)%3==0;
}
};
3.交换数字
问题描述
编写一个函数,不用临时变量,直接交换numbers = [a, b]
中a
与b
的值。
输入输出样例
输入: numbers = [1,2]
输出: [2,1]
解题思路与代码
使用以下三步可以将a和b交换:
(1)将a^b赋值给a
(2)将a^b赋值给b,将(1)带入得b=a ^ b ^ b,又b ^ b=0,所以b=a ^ 0=a
(3)将a ^ b赋值给a,将(2)带入得a=a ^ b=a ^ a ^ b=0^b=b
class Solution {
public:
vector<int> swapNumbers(vector<int>& numbers) {
numbers[0]=numbers[0]^numbers[1];
numbers[1]=numbers[0]^numbers[1];
numbers[0]=numbers[0]^numbers[1];
return numbers;
}
};
4.只出现一次的数字
问题描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
输入输出样例
输入: [2,2,1]
输出: 1
解题思路与代码
可以考虑使用a ^ a=0和a^ 0=a这两个性质解题,对于数组中的每个数依次进行^操作,根据交换律的原则,最后会剩下只出现一次的那个数。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int sum=0;
for(int i=0;i<nums.size();i++)
{
sum=sum^nums[i];
}
return sum;
}
};
5.汉明距离
问题描述
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。给你两个整数 x
和 y
,计算并返回它们之间的汉明距离。
输入输出样例
输入:x = 1, y = 4
输出:2
解题思路与代码
只需要考虑x^y之后的结果中有多少个1出现就行。对于统计1出现次数的问题又可以参考例题1的方法,这里就不再赘述。
class Solution {
public:
int hammingDistance(int x, int y) {
int n=x^y;
int count=0;
while(n)
{
n&=n-1;
count++;
}
return count;
}
};
6.交替位二进制数
问题描述
给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。
输入输出样例
输入:n = 5
输出:true
解释:5 的二进制表示是:101
解题思路与代码
可以反过来考虑,就是检测二进制数中是否有00或者11的情况出现,00对应的十进制数为0,11对应的十进制数为3。因此可以考虑使用0和3以及&运算来搜索整个二进制序列。
class Solution {
public:
bool hasAlternatingBits(int n) {
while(n)
{
if((n&3)==3||(n&3)==0)
return false;
n=n>>1; //n>>=1;
}
return true;
}
};
7.两整数之和
问题描述
给你两个整数 a
和 b
,不使用 运算符 +
和 -
,计算并返回两整数之和。
输入输出样例
输入:a = 1, b = 2
输出:3
解题思路与代码
首先,a ^ b可以看成是一种不进位的+操作,然后对a&b左移一位又可以得到进位的那个数,因此只要将a^b+(a&b)<<1就可以得到a+b,但是因为不能用+,因此可以考虑使用递归,而递归最后结束的条件是b==0。
class Solution {
public:
int getSum(int a, int b) {
return b==0?a:getSum(a^b,(unsigned int)(a&b)<<1);
}
};
8.颠倒二进制位
问题描述
给定一个十进制整数,输出它在二进制下的翻转结果。
输入输出样例
输入和输出都是十进制整数。
输入: 43261596 (00000010100101000001111010011100)
输出: 964176192 (00111001011110000010100101000000)
解题思路与代码
使用算术左移和右移,可以很轻易地实现二进制的翻转。
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t ans = 0;
for (int i = 0; i < 32; ++i) {
//使用01模板获取n的每一位,push进ans中
ans <<= 1;
ans += n & 1;
n >>= 1;
}
return ans;
}
};
9.最大单词长度乘积
问题描述
给你一个字符串数组 words ,找出并返回 length(words[i])*length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0 。
输入输出样例
输入:words = [“abcw”,“baz”,“foo”,“bar”,“xtfn”,“abcdef”]
输出:16
解释:这两个单词为 “abcw”, “xtfn”。
解题思路与代码
怎样快速判断两个字母串是否含有重复数字呢?可以为每个字母串建立一个长度为 26 的二进制数字,每个位置表示是否存在该字母。如果两个字母串含有重复数字,那它们的二进制表示的按位与不为 0。同时,我们可以建立一个哈希表来存储字母串(在数组的位置)到二进制数字的映射关系,方便查找调用。
class Solution {
public:
int maxProduct(vector<string>& words) {
unordered_map<int, int> hash;
int ans = 0;
for (const string & word : words) {
int mask = 0, size = word.size();
for (const char & c : word) {
mask |= 1 << (c - 'a');
}
hash[mask] = max(hash[mask], size);
for (const auto& [h_mask, h_len]: hash) {
if (!(mask & h_mask)) {
ans = max(ans, size * h_len);
}
}
}
return ans;
}
};
总结
以上就是《位运算》的全部内容,文章会持续更新,如果文章中有写的不对的地方,希望大家可以在评论区进行批评和指正,大家一起交流,共同进步!