位运算的操作对象只能是整型或字符型数据。
位求反~:将运算对象逐位求反生成一个新值,将1置为0,将0置为1.
与(&):两个都为1,结果才为1;
或(|):只要一个为1,结果就为1;
异或(^):两个相同,结果为0,两个不同,结果为1.
左移运算符<<:m<<n表示把m左移n位。在左移n位的时候,最左边的n位将被丢弃,同时在最右边补上n个0;
右移运算符>>:m>>n表示把m右移n位。在右移的时候,最右边的n位将被丢弃。如果数字是一个无符号数值,则用0填补最左边的n位,如果数字是一个有符号数值,则用数字的符号位填补最左边的n位。也就是说,如果是正数,最左边补n个0,如果是负数,最左边补n个1.
这里介绍一下负数的二进制表示方法:
原码:一个正数,按照绝对值大小转换成的二进制数;一个负数按照绝对值大小转换成的二进制数,然后最高位补1,称为原码。
反码:正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。
补码:正数的补码与原码相同,负数的补码为反码的最后一位加1,即对该数的原码除符号位外各位取反,然后在最后一位加1.
例如:(int类型的数占4字节,即32位,所以二进制表示的前面有很多0)
-5的原码为:10000000 00000000 00000000 00000101;
-5的反码为:11111111 11111111 11111111 11111010;
-5的补码为:11111111 11111111 11111111 11111011 ;
所以,-5在计算机中表达为:11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。
负数的二进制与其绝对值的二进制关系为:-x = !x + 1。
常用操作
(1) 将第n位(从右边开始数,初始为0)置为1: x |= (1 << n);
(2)将第n位置为0:x &= ~(1 << n);
(3)得到第n位:x & (1 << n);
(4)消去二进制中最右侧的1:x & (x-1);
(5)检查n是否为2的幂次位:即二进制中只有一个1,即判断消去1后是否为0:x & (x - 1) == 0;
(6)x & (-x) 是x的最右边一个1的位置对应的数。最右边的1后面所有的位都是0,将其取反后1变成了0,后面的0都变成了1,再将其加1,最右边的1之前的所有位都与原来相反,再与原来的数进行&操作,便得到了最右边1的位置。即x & (!x + 1),即x & (-x)。
(7)正整数的按位取反是其本身+1的负数;负整数的按位取反是其本身+1的绝对值;零的按位取反是 -1。
1. 汉明距离
题目描述:两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。给出两个整数 x 和 y,计算它们之间的汉明距离。
解题思路:对这两个数进行异或操作,如果某位相同,则为0,不同,则为1。然后统计1出现的次数,计数方法利用的是n&(n-1)将最后一个1变为0.(或者循环右移,每次移1位,判断最后一位是否为1,若为1,则计数加1)
class Solution {
public:
int hammingDistance(int x, int y) {
int tmp = x ^ y;
int cnt = 0;
while (tmp != 0){
cnt++;
tmp = tmp & (tmp - 1);
}
return cnt;
}
};
2. 数组中唯一一个不重复的元素(NO.136)
题目描述:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
解题思路:利用异或操作。主要利用:交换律:a ^ b ^ c = a ^ c ^ b;任何数与0异或为原数: 0 ^ n = n;相同的数异或为0: n ^ n = 0。所以,一个数对另一个数进行两次异或操作,还等于原来的数。
2 ^ 3 ^ 2 ^ 4 ^ 4等价于 2 ^ 2 ^ 4 ^ 4 ^ 3 => 0 ^ 0 ^3 => 3
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
for (int num : nums)
res ^= num;
return res;
}
};
3. 找出数组中缺失的那个数(NO.268)
题目描述:给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 … n 中没有出现在序列中的那个数。
解题思路一:使用位运算。将数组中的所有数与0~n序列中的数进行异或操作。
class Solution {
public:
int missingNumber(vector<int>& nums) {
int res = nums.size();
for (int i = 0; i < nums.size(); i++)
res ^= i ^ nums[i];
return res;
}
};
解题思路二:求0到n数列的和,减去数组中的所有数,便得到缺失数。
class Solution {
public:
int missingNumber(vector<int>& nums) {
int sum = 0;
int i;
for (i = 0; i < nums.size(); i++)
sum += i-nums[i];
sum += i;
return sum;
}
};
4. 数组中不重复的两个元素(NO.260)
题目描述:
解题思路一:使用哈希表计数。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
vector<int> res;
unordered_map<int,int> mymap;
for (int i = 0; i < nums.size(); i++)
mymap[nums[i]]++;
for (auto pair : mymap){
if (pair.second == 1)
res.push_back(pair.first);
}
return res;
}
};
解题思路二:使用位运算。遍历两次数组:第一次遍历:对数组中的所有值进行异或操作,得到这两个数的异或值diff。通过diff&(-diff)得到diff最右边1的位置,从而确定这两个数最右侧不同的位,并进行第二次遍历,遍历时根据该位将数组分为两部分。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int diff = 0;
for (int num : nums)
diff ^= num;//得到这两个数字的异或值
diff &= -diff;//得到这两个数最右侧不同的位
vector<int> res(2,0);
for (int num : nums){
if ((num & diff) == 0)//对该位为0的所有数取异或
res[0] ^= num;
else//对该位为0的所有数取异或
res[1] ^= num;
}
return res;
}
};
5. 翻转一个数的比特位(NO.190)
题目描述:颠倒给定的 32 位无符号整数的二进制位。
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t res = 0;
for (int i = 1; i <= 32; i++){
res <<= 1;//注意先左移,在更改最后一位
res |= (n & 1);
n >>= 1;
}
return res;
}
};
6. 不用额外变量交换两个整数
解题思路:
a = a ^ b;
b = a ^ b = a ^ b ^ b = a;
a = a ^ b = a ^ b ^ a = b;
7. 判断一个数是不是 2 的 n 次方(NO.231)
解题思路:2的n次方的二进制表示中只有一个1。
class Solution {
public:
bool isPowerOfTwo(int n) {
return n > 0 && (n & n-1) == 0;
}
};
8. 判断一个数是不是 4 的 n 次方(NO.342)
解题思路:二进制表示中只有一个1;一直除以4最后会得到1.
class Solution {
public:
bool isPowerOfFour(int num) {
if (num <= 0 || (num & num-1) != 0)
return false;
while (num != 0){
if ((num & 1) == 1)
return true;
num >>= 2;//除以4
}
return false;
}
};
9. 交替位二进制数(NO.693)
题目描述:给定一个正整数,检查他是否为交替位二进制数:换句话说,就是他的二进制数相邻的两个位数永不相等。例如:10101010
解题思路:如果是交替位二进制数,则将该数右移一位后与原数的各位都不相同,判断n^(n>>1)的所有位是否都是1即可.
class Solution {
public:
bool hasAlternatingBits(int n) {
long a = n ^ (n >> 1);//a+1可能超出int型范围,所以用long
return (a & (a+1)) == 0;
}
};
10. 求一个数的补码(NO.476)
题目描述:给定一个正整数,输出它的补数。补数是对该数的二进制表示取反。
解题思路:对于 00000101,求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。求掩码:找到原数二进制最左边1的位置,即求得00000100,然后再左移一位再减1.这里介绍两种方法求最左边1的位置:
(1)令mask的第一位为1,让其右移,当num&mask!=0时,便找到了该位置;
(2)令mask的最后一位为1,当num!=0时,让其左移一位,num右移一位,循环至num为0,此时mask中1的位置为num最左边第一个1的位置的左边一位。
class Solution {
public:
int findComplement(int num) {
if (num == 0)
return 1;
//第一种
long mask = 1 << 30;
while ((mask & num) == 0)
mask >>= 1;
mask = (mask << 1) - 1;
/*第二种
long mask = 1;
int tmp = num;
while (tmp != 0){
mask <<= 1;
tmp >>= 1;
}
mask -= 1;
/*
return (mask ^ num);
}
};
11.实现整数的加法(NO.371)
题目描述:不使用运算符 + 和 - ,计算两整数 a 、b 之和。a、b可正可负。
解题思路:a + b 的问题拆分为 (a 和 b 的无进位结果) + (a 和 b 的进位结果);无进位加法使用异或运算计算得出;进位结果使用与运算和移位运算计算得出;循环此过程,直到进位为 0。
class Solution {
public:
int getSum(int a, int b) {
while (b != 0){
int tmp1 = (unsigned int)(a & b) << 1;
a = a ^ b;
b = tmp1;
}
return a;
}
};
12. 字符串数组最大乘积(NO.318)
题目描述:给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。
输入: [“abcw”,“baz”,“foo”,“bar”,“xtfn”,“abcdef”];输出: 16 ;解释: 这两个单词为 “abcw”, “xtfn”。
解题思路:主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。例如,如果字符a出现在单词i中,则vec[i]的二进制表示的最后一位为1.
class Solution {
public:
int maxProduct(vector<string>& words) {
int n = words.size();
vector<int> vec(n,0);
for (int i = 0; i < n; i++){
for (auto c : words[i])
vec[i] |= 1 << (c - 'a');
}
int max = 0;
for (int i = 0; i < n; i++){
for (int j = i + 1; j < n; j++){
if ((vec[i] & vec[j]) == 0){
int tmp = words[i].size() * words[j].size();
max = max > tmp ? max : tmp;
}
}
}
return max;
}
};
13 统计从 0 ~ n 每个数的二进制表示中 1 的个数(NO.338)
题目描述:
解题思路一:利用n&(n-1)将n的最后一个1消除,进行计数。
class Solution {
public:
int count(int n){
if (n == 0)
return 0;
int res = 0;
while (n != 0){
res++;
n &= (n-1);
}
return res;
}
vector<int> countBits(int num) {
vector<int> vec(num+1,0);
for (int i = 1; i <= num; i++)
vec[i] = count(i);
return vec;
}
};
解题思路二:利用动态规划的思想。n&(n-1)可以将n的最后一个1消去,则n中1的个数比n&(n-1)中1的个数多出1个,即dp[i]=dp[i&(i-1)]+1.
class Solution {
public:
vector<int> countBits(int num) {
vector<int> vec(num+1,0);
for (int i = 1; i <= num; i++)
vec[i] = vec[i&(i-1)] + 1;
return vec;
}
};