常见位运算题目
常用的位运算符号包括:
- “∧”按位异或
- “&”按位与
- “|”按位或
- “∼”取反
- “<<” 算术左移 是带符号左移
- “>>” 算术右移 是带符号右移,保持符号不变
逻辑左移=算数左移,右边统一添0
逻辑右移,左边统一添0
算数右移,左边添加的数和符号有关
C/C++语言中逻辑右移和算数右移共享同一个运算符>>。
编译器决定使用逻辑右移还是算数右移,根据的是运算数的类型。
如果运算数类型是unsigned则采用逻辑右移,而signed则采用算数右移。
对于signed类型的数据,如果需要使用算数右移,或者unsigned类型的数据需要使用逻辑右移,都需要进行类型转换。
(unsigned int)a >> 1
(int)a >> 1
- n & (n - 1) 可以去除 n 的位级表示中最低的那一位
- n & (-n)可以获取n的最低一位非0位
(a & (-a) 可以获得a最低的非0位 ,比如a的二进制是 ???10000,取反就是???01111,加1就是???10000。前面?的部分是和原来a相反的,相与必然都是0,所以最后整体相与的结果就是00000010000。)
位运算基础问题
461、汉明距离
两个数字之间的汉明距离 = 他们的二进制不同位置的数目
class Solution {
public:
int hammingDistance(int x, int y) {
int ans = 0;
//使用位运算
int diff = x ^ y;
while(diff){
ans += diff & 1;
diff = (unsigned)diff>>1;
//diff >>= 1;
}
return ans;
}
};
注意要 diff = diff>>1 或者 diff >>= 1;
不然diff的值就不会变更
190、颠倒二进制位
给了一个32位的无符号数,将其前后颠倒后返回。
解法:可以将写一个32位的循环,每个循环中:
将ret左移一位,将当前的n的最后一位赋值给ret的最后一位,将n右移一位。
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t ret = 0;
for(int i = 0;i<32;i++){
ret <<= 1;
ret += n&1;
n >>= 1;
}
return ret;
}
};
136、只出现一次的数字
给了一个非空整数数组,其中只有一个元素只出现了一次,其他的元素都出现了两次。找出只出现了一次的元素
解答:出现了两次的都可以消除掉,最后只留下那个出现了一次的。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for(int num:nums){
ret = ret ^ num;
}
return ret;
}
};
268、丢失的数组
是只出现一次的数字的改版,给了一个含n个数字的数组,求从0-n中它缺少哪一个数。
解答:可以利用只出现一次的数字的思路,如果把这个数组与0-n的所有数取^,那么其他的会被去掉。
class Solution {
public:
int missingNumber(vector<int>& nums) {
if(nums.size()==0)return 0;
int ret = 0;
for(int i = 1;i<=nums.size();i++){
ret = ret ^ i;
}
for(int t:nums){
ret = ret ^ t;
}
return ret;
}
};
693、交替位二进制数
给了一个n,需要判断这个n的二进制表示中是否是交替的0和1组成。
class Solution {
public:
bool hasAlternatingBits(int n) {
//把数字的每一位和后一位相异或,只有一直为1 才是满足条件的。
int t = 0;
while(n!=0){
t = n&1;
n = n>>1;
if(!(t^(n&1)))return false;
}
return true;
}
};
二进制特性
可以利用二进制特性,把二进制运算用在一些题目上,如获取这个数组的全部子集。
342、4的幂
给一个整数,判断它是否是4的幂
解答:注意到2的幂应该是2进制里面只有一个1,4的幂在此基础上还需要1所在的位置为奇数。
class Solution {
public:
bool isPowerOfFour(int n) {
//注意输入为整数,因此不用考虑小数的情况,4^x中的x应该为正数。
//4^0 = 00001,4^1 = 0100,4^2 = 010000,每增加一个,这个1要往左移动一位。,因此1只在奇数位置出现-在所有偶数地方不出现。
//4的幂 = 2的幂(数字位只有一个1)+ (只有奇数位置为1)
if(n>0&&((n&(n-1))==0)&&((n&0xaaaaaaaa)==0))return true;
return false;
//注意&的优先级比==低
}
};
判断是否为2的幂可以通过n&(n-1),这个操作可以去掉n的最后一位的值(前提是正数)。
因此如果是2的幂,那么去掉这一位后就没有了,n&(n-1)==0。
318、最大单词长度乘积
给了一个单词数组,返回其中两个没有重复字符的单词的乘积最大值。
解题:问题需要如何快速的比较两个字符串之间是否有重复字符,可以为每个字符串设置一个int值,其转化为的二进制中的每一位,表示字符串中是否包含字母表中的该字母。
使用vector存储每个字符串的长度、以及该字符串对应的int值
class Solution {
public:
int maxProduct(vector<string>& words) {
//特判
if(words.size()<=1)return 0;
//快速比较两个字符串是否有重复的数字
//可以使用一个26位的二进制,表示每个字母他是否拥有,若两个字符串对应的二进制&值为0,则没有公共字母
vector<int> len;
vector<int> mask;
for(string word:words){
len.push_back(word.size());
//转换为二进制后,a在最后一位
int mask_tmp = 0;
for(char c:word){
mask_tmp = mask_tmp | 1<<(c-'a');
}
mask.push_back(mask_tmp);
}
int ret = 0;
for(int i = 0;i< words.size()-1;i++){
for(int j = i+1;j<words.size();j++){
if((mask[i]&mask[j])==0){
ret = max(ret,len[i]*len[j]);
}
}
}
return ret;
}
};
338、比特位计数
给了一个n,返回一个数组,其中包含了从0-n的所有数字的二进制所包含的1的个数。
解题:1、暴力解题,一个一个数字计算过去。
2、使用dp的方法,获取每个位置值的关系
如果一个数的最后一位是1,那么它的1个数应该是上一个数1个数+1,否则的话应该等于它右移一位的值(把这个1移掉后值是相同的)
class Solution {
public:
vector<int> countBits(int n) {
//方法1:eng算
// vector<int> ret(n+1,0);
// for(int i =0;i<=n;i++){
// unsigned cur = i;
// int count = 0;
// while(cur!=0){
// count += cur&1;
// cur = cur>>1;
// }
// ret[i] = count;
// }
// return ret;
//方法二:使用dp
vector<int> ret(n+1,0);
ret[0] = 0;
if(n==0)return ret;
ret[1] = 1;
if(n==1)return ret;
for(int i =2;i<=n;i++){
if((i&1)==1){
ret[i] = ret[i-1]+1;
}else{
ret[i] = ret[i>>1];
}
}
return ret;
}
};
476、数字的补数
给了一个正整数,求它从第一位非0位开始往后的所有位的二进制值取反。
解法:
想要取反,则需要与1取异或,但是需要注意的是只有从第一位二进制非0位开始取反,那么需要将1的个数和二进制有值的地方对齐。然后进行异或。
class Solution {
public:
int findComplement(int num) {
//需要找到与num长度相同的二进制的全1值
unsigned mask = -1;
//注意需要使用unsigned,不然会溢出
while((mask & num) > 0)
{
mask <<= 1;
}
mask = ~mask;
return num ^ mask;
}
};
注意:想要找到长度与num相同的全为1的串,可以由前面全为1的数各个位全部取反获得。二进制的取反操作~。
获得前面全为1的数可以通过不断的左移动,看是否与当前数相与 =0 如果说等于0的话,说明此时的这个移位得到的数已经满足条件了。
260、只出现一次的数字III
给了一个数组,其中只有两个元素只出现了一次,其他的元素都出现了两次。
解答:思路是想办法把这两个元素放到不同的数组里面,希望把他们拆分为两个不同的数组,分别进行处理。
拆分的方法就是首先取所有的异或,就可以获得所有数字的异或值,然后找到最后一位值为1的(用mask & -mask获得),然后通过此来把两个只有一个的元素分到两个不同的分组中去。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
//首先找到这两个元素不同的最低的不同位
unsigned ret = 0;
for(int t:nums){
ret = ret ^ t;
}
unsigned mask = ret & (-ret);
vector<vector<int>> vec(2,vector<int>(nums.size(),0));
for(int t :nums){
if((mask & t)==0){
vec[0].push_back(t);
}else{
vec[1].push_back(t);
}
}
vector<int> ans;
//分别对每一组进行求解
int tmp = 0;
for(int t: vec[0]){
tmp = tmp ^ t;
}
ans.push_back(tmp);
tmp = 0;
for(int t: vec[1]){
tmp = tmp ^ t;
}
ans.push_back(tmp);
return ans;
}
};