目录
位运算常用操作:
1、 191. 位1的个数
2、 338. 比特位计数
3、 461. 汉明距离
4、136. 只出现一次的数字
5、 260. 只出现一次的数字 III
6、面试题 01.01. 判定字符是否唯一
7、 268. 丢失的数字
8、 371. 两整数之和
9、 137. 只出现一次的数字 II
10、面试题 17.19. 消失的两个数字
位运算常用操作:
- << 二进制左移一位
- >> 二进制右移一位
- ~ 按位取反
- & 有0则0
- | 有1则1
- ^ 相同为0,相异为1,无进位相加
2.给一个数n,确定它的二进制表示中的第x位是0还是1
- (n>>x)&1或者n&(1<<x)
3.将一个数n的二进制表示的第x位修改成1
- n|=(1<<x)
4.将一个数n的二进制表示的第x位修改成0
- n&=(~(1<<x))
5.位图的思想
- 例如:使用整形变量int的每一个二进制位进行记录
6.提取一个数(n)二进制表示中最右侧的1
- n&-n
7.干掉一个数(n)二进制表示中最右侧的1
- n&=(n-1)
8.位运算的优先级
- 加括号解决
9.异或(^)运算的运算律
- a^a=0
- a^0=a
- a^b^c=a^(b^c)
1、 191. 位1的个数
思路:干掉一个数(n)二进制表示中最右侧的1
- n&=(n-1)
class Solution {
public:
int hammingWeight(uint32_t n) {
int ret = 0;
while (n) {
n &= (n - 1);
ret++;
}
return ret;
}
};
2、 338. 比特位计数
思路:干掉一个数(n)二进制表示中最右侧的1 n&=(n-1)
class Solution {
public:
vector<int> countBits(int n) {
vector<int> a(n+1);
for (int i = 0; i <= n; i++) {
int ret = 0;
int n = i;
while (n) {
n &= (n - 1);
ret++;
}
a[i]=ret;
}
return a;
}
};
3、 461. 汉明距离
思路:干掉一个数(n)二进制表示中最右侧的1 n&=(n-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;
}
};
4、136. 只出现一次的数字
思路:异或,所以元素进行异或,出现两次的会变为0,0不影响异或结果,所以剩下的就是出现一次的数。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int v=0;
for(auto a:nums)
{
v^=a;
}
return v;
}
};
5、 260. 只出现一次的数字 III
思路:所有元素异或后,取结果中任意一个二进制位为1的那一位,一定存在两组数,一组这一位为1,另一组这一位为0,由这一位可以分出两组数,这两组数中出现两次的数会抵消为0,最后两组分别剩下的就是出现一次的数。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int sum = 0;
for (int num : nums) {
sum ^= num;
}
// 防止溢出
int l = (sum == INT_MIN ? sum : sum & (-sum));
int num1 = 0, num2 = 0;
for (int num : nums) {
if (num & l)
num1 ^= num;
else
num2 ^= num;
}
return {num1, num2};
}
};
-
首先,
sum
是通过对数组中的所有元素进行异或操作得到的。由于其他元素都出现了两次,所以异或操作会将相同的元素抵消掉,最终得到的结果就是只出现一次的两个元素的异或值。 -
sum & (-sum)
的作用是获取sum
的最低位的 1 所对应的值。-sum
是sum
的补码的负数,它的二进制表示中只有最低位的 1 是相同的,其他位都是相反的。通过与操作sum & (-sum)
,可以将sum
的二进制表示中除了最低位的 1 以外的其他位都置为 0,得到的结果就是最低位的 1 所对应的值。
这样,l
就表示了只出现一次的两个元素在最低位的不同之处。在后续的循环中,根据每个元素的最低位是否与 l
相同,将元素分为两组,分别进行异或操作,最终得到的 num1
和 num2
就是只出现一次的两个元素。
需要注意的是,由于
INT_MIN
的二进制表示中只有最高位是 1,其他位都是 0,所以在计算l
的时候需要特殊处理sum == INT_MIN
的情况,因为-INT_MIN
会导致溢出,以确保最低位的 1 能够正确地被提取出来。(在一个32位系统中,int
类型的数值范围通常是从-2^31
到2^31 - 1
,也就是从-2147483648
到2147483647
。)-INT_MIN为2^31大于整形最大值2^31-1.
INT_MIN
的二进制形式是一个以 1 开头,后面跟着 31 个 0 的二进制数。在大多数平台上,INT_MIN
的二进制表示为:10000000000000000000000000000000
这是一个带符号的 32 位整数,最高位为 1,表示负数,其余位为 0。这是 int
类型能够表示的最小值。
6、面试题 01.01. 判定字符是否唯一
思路:位图思想,int类型变量有32个比特位,选取26个比特位记录字母判断是否每个字母只出现一次。
class Solution {
public:
bool isUnique(string astr) {
if (astr.size() > 26)
return false;
int bitmap = 0;
for (auto s : astr) {
int i = s - 'a';
if ((bitmap >> i) & 1 == 1)
return false;
else
bitmap |= (1 << i);
}
return true;
}
};
假设我们有一个字符串 astr = "abcde"
,并且我们使用一个 32 位的整数 bitmap
来表示字符是否出现过。在这个例子中,每个字符都对应一个整数,假设 'a' 对应 0,'b' 对应 1,以此类推。
初始时,bitmap
的二进制表示是 000...000
,全部为 0。然后我们遍历字符串 astr
中的每个字符,检查是否出现过。
-
对于字符 'a':
- 字符 'a' 对应整数 0。
- 计算
bitmap >> 0
,结果还是000...000
。 - 执行
(bitmap >> 0) & 1
,结果为 0,说明 'a' 还没有出现过。 - 将
bitmap |= (1 << 0)
,将 'a' 对应的位置设为 1,此时bitmap
的二进制表示变为000...001
。
-
对于字符 'b':
- 字符 'b' 对应整数 1。
- 计算
bitmap >> 1
,结果为000...000
。 - 执行
(bitmap >> 1) & 1
,结果为 0,说明 'b' 还没有出现过。 - 将
bitmap |= (1 << 1)
,将 'b' 对应的位置设为 1,此时bitmap
的二进制表示变为000...011
。
-
对于字符 'c':
- 字符 'c' 对应整数 2。
- 计算
bitmap >> 2
,结果为000...000
。 - 执行
(bitmap >> 2) & 1
,结果为 0,说明 'c' 还没有出现过。 - 将
bitmap |= (1 << 2)
,将 'c' 对应的位置设为 1,此时bitmap
的二进制表示变为000...111
。
依此类推,遍历完字符串后,bitmap
的二进制表示可能变为 000...111
,因为所有字符都出现过。如果字符串长度超过 26,说明一定有重复字符,那么函数会返回 false
。
7、 268. 丢失的数字
思路:两次异或
class Solution {
public:
int missingNumber(vector<int>& nums) {
int ret = 0;
for (auto x : nums)
ret ^= x;
for (int i = 0; i <= nums.size(); i++)
ret ^= i;
return ret;
}
};
8、 371. 两整数之和
class Solution {
public:
int getSum(int a, int b) {
while (b != 0) {
int ret = a ^ b;
unsigned int carry = (unsigned int)(a & b) << 1;
a = ret;
b = carry;
}
return a;
}
};
9、 137. 只出现一次的数字 II
思路:
- 创建一个长度为 32 的整数数组
count
,用于记录每一位上的出现次数。- 遍历数组
nums
中的每个元素,对每个元素的每一位进行统计,即将每个元素的每一位与1
进行与操作,然后根据结果更新对应位的计数器。- 对每一位上的计数器进行模 3 操作,因为除了只出现一次的元素,其他元素都出现三次,所以对每一位上的计数取模 3 之后,剩余的结果就是只出现一次的元素在该位上的值。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (int i = 0; i < 32; i++) {
int sum = 0;
for (auto n : nums) {
if (((n >> i) & 1) == 1)
sum++;
}
sum %= 3;
if (sum == 1)
ret |= 1 << i;
}
return ret;
}
};
10、面试题 17.19. 消失的两个数字
思路:
异或操作统计缺失的两个数字:
- 我们首先遍历给定数组
nums
,对其中的每个元素执行异或操作,得到一个结果tmp
。由于数组中缺失了两个数字,所以tmp
最终将包含这两个缺失的数字的异或结果。- 接着,我们对从 1 到 N+2 的所有整数执行异或操作,得到的结果也存储在
tmp
中。这样,tmp
中存储的就是缺失的两个数字的异或结果,因为在从 1 到 N+2 的所有整数中,只有这两个数字出现了奇数次,其他数字出现了偶数次,所以它们会保留下来。确定两个缺失的数字:
- 接下来,我们需要找到
tmp
中哪一位上的值为 1,因为在这一位上,两个缺失的数字不同,通过这一位可以将它们区分开来。我们通过一个循环来找到这个位置d
,使得tmp
的第d
位为 1。- 然后,我们再次遍历数组
nums
和从 1 到 N+2 的所有整数,根据第d
位的值将它们分成两组,分别执行异或操作,最终得到的就是两个缺失的数字a
和b
。返回结果:
- 最后,我们将
a
和b
组成一个数组返回即可。
class Solution {
public:
vector<int> missingTwo(vector<int>& nums) {
int tmp = 0;
for (auto x : nums) {
tmp ^= x;
}
for (int i = 1; i <= nums.size() + 2; i++) {
tmp ^= i;
}
int d = 0;
while (((tmp >> d) & 1) != 1) {
d++;
}
int a = 0, b = 0;
for (auto x : nums) {
if (((x >> d) & 1) == 1)
a ^= x;
else
b ^= x;
}
for (int i = 1; i <= nums.size() + 2; i++) {
if (((i >> d) & 1) == 1)
a ^= i;
else
b ^= i;
}
return {a, b};
}
};