【算法】位运算
目录
01.?整数在内存中的存储
整数在计算机中以二进制数表示。
整数表示的范围在头文件limits.h中定义。
01.1?无符号整数的表示方法
unsigned int a = 25;
00000000000000000000000000011001? ? ? ?1×2^4+1×2^3+0×2^2+0×2^1+1×2^0=25
高位<------------------------------------->低位
n位可以表示的无符号整数的范围是0~
无符号整数类型 | 最小值 | 最大值 |
---|---|---|
unsigned char | 0 | 255 |
unsigned short | 0 | 65535 |
unsigned int | 0 | 4294967295 |
01.2?有符号整数的表示方法
有符号整数有3种二进制表示方法,即原码、反码和补码。最高位为符号位,0表示正数,1表示负数,其余为数值位。在计算机中整数以补码的形式存储。
原码:直接将数值按照正负数的形式翻译成二进制。
正数的反码、补码与原码相同。负数的反码、补码要经过如下计算。
反码:将原码的符号位不变,其他位依次按位取反。补码:反码+1就得到补码。
int a = -25;
// 原码:10000000000000000000000000011001
// 反码:11111111111111111111111111100110
// 补码:11111111111111111111111111100111
8位有符号整数的取值范围如下图所示:(补码10000000不表示-0,表示-128)
?n位可以表示的无符号整数的范围是~
有符号整数类型 | 最小值 | 最大值 |
---|---|---|
signed char | -128 | 127 |
signed short | -32768 | 32767 |
signed int | -2147483648 | 2147483647 |
02. 移位操作符
02.1 左移操作符
整数的二进制表示有3种:原码、反码、补码。正整数的原码、反码、补码相同。负整数的反码是原码符号位不变,其他位按位取反,补码是反码+1。整数在内存中存储的是补码。
+7:
原码:00000000000000000000000000000111
反码:00000000000000000000000000000111
补码:00000000000000000000000000000111
-7:
原码:10000000000000000000000000000111
反码:11111111111111111111111111111000
补码:11111111111111111111111111111001
移位规则:左边抛弃,右边补0
int a = 7;
int b = a << 1;
0[00000000000000000000000000001110]
int a = -7;
int b = a << 1;
1[11111111111111111111111111110010]
1 << n = 2的n次幂
int a = 1;
int b = a << 1;
0[00000000000000000000000000000010]=2的1次幂
int b = a << 2;
00[00000000000000000000000000000100]=2的2次幂
int b = a << 3;
000[00000000000000000000000000001000]=2的3次幂
所以,1<<n=2的n次幂
02.2 右移操作符
移位规则:
- 逻辑移位:左边补0,右边抛弃
- 算术移位:左边补原符号位,右边抛弃(常见编译器都是算术移位)
算术移位:
int a = 7;
int b = a >> 1;
[00000000000000000000000000000011]1
int a = -7;
int b = a >> 1;
[11111111111111111111111111111100]1
n >> 1 和 n / 2
n为非负数时,n>>1=n/2??????????10>>1=5??????10/2=5,0>>1=0??????0/2=0
n为负偶数时,n>>1=n/2??????????-10>>1=-5??-10/2=-5
n为负奇数时,n>>1=n/2-1???????-5>>1=-3??????-5/2=-2(n>>1是n除以2向下取整,n/2是n除以2向上取整)
03. 位操作符
&按位与,|按位或,^按位异或
位操作符通过逐位比较两个运算对象,生成一个新值。对于每个位:
- &:两个操作数相应的位都为1,结果为1
- |?:两个操作数相应的位至少有一个为1,结果为1
- ^:两个操作数相应的位相同为0,相异为1
03.1 按位与
int a = 3;
int b = -5;
int c = a &?b;
3的补码:00000000000000000000000000000011
-5的补码:11111111111111111111111111111011
c的补码:00000000000000000000000000000011
n & 1 和 n % 2
n为非负数时,n&1=n%2
- n为奇数:n&1=1? n%2=1
- n为偶数:n&1=0? n%2=0
n为负数时,
- n为奇数:n&1=1? n%2=-1
- n为偶数:n&1=0? n%2=0
n >> i & 1
n>>i&1用来获取二进制的每一位。
以75(000000000000000000000001001011)为例:
要想获取最后一位,就要&1,即
000000000000000000000001001011
&000000000000000000000000000001
=000000000000000000000000000001
=1
要想获取倒数第二位,就要>>1,再&1,即
75>>1=[000000000000000000000000100101]1
000000000000000000000000100101
&000000000000000000000000000001
=000000000000000000000000000001
=1
要想获取倒数第三位,就要>>2,再&1,即
75>>2=[000000000000000000000000010010]11
000000000000000000000000010010
&000000000000000000000000000001
=000000000000000000000000000000
=0
n & (n - 1)
n-1表示将n的二进制位最右边的1变为0,该1右边的所有0都变为1。
n&(n-1)表示将n的二进制位最右边的1变为0,其余位不变。
24:? ? ?? 00000000000000000000000000011000
23:? ? ???00000000000000000000000000010111
24&23:00000000000000000000000000010000
如果n&(n-1)为0,则n是2的幂。
+2:? 00000000000000000000000000000010
+1:? 00000000000000000000000000000001
+4:? 00000000000000000000000000000100
+3:? 00000000000000000000000000000011
2&1:00000000000000000000000000000000
4&3:00000000000000000000000000000000
03.2 按位或
int a = 3;
int b = -5;
int c = a |?b;
3的补码:00000000000000000000000000000011
-5的补码:11111111111111111111111111111011
c的补码:11111111111111111111111111111011
03.3 按位异或
int a = 3;
int b = -5;
int c = a ^?b;
3的补码:00000000000000000000000000000011
-5的补码:11111111111111111111111111111011
c的补码:11111111111111111111111111111000
a ^ a = 0? ? 0 ^ a = a
a^a=0:
如,3^3=00000011^00000011=00000000
0^a=a:
如,0^3=00000000^00000011=00000011
不创建临时变量实现两个数的交换:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a ^ b; // 10^20
b = a ^ b; // 10^20^20=10^0=10(^操作符支持交换律)
a = a ^ b; // 10^20^10=0^20=20
printf("a = %d b = %d\n", a, b);
return 0;
}
1. 比特位计数(简单)
方法一:
vector<int> countBits(int n)
{
vector<int> ret(n + 1, 0); // 创建n+1个元素的数组,将所有元素初始化为0
// 0的二进制形式中没有1,现在ret[0]的值已经为0,所以可以从1开始计算
for (int i = 1; i <= n; i++)
{
int n = i;
while (n)
{
ret[i]++;
// 每进行一次n&(n-1)的操作,就能去掉最后一个1,去掉所有的1时(n为0时)结束循环
// 二进制位1的个数=循环的次数
n = n & (n - 1);
}
}
return ret;
}
如果一个整数共有k位,对于每个整数,while循环最多循环k次,每次循环的时间复杂度为O(1),因此上述代码的时间复杂度为O(kn)。
方法二:
vector<int> countBits(int n)
{
vector<int> ret(n + 1, 0); // 创建n+1个元素的数组,将所有元素初始化为0
// 0的二进制形式中没有1,现在ret[0]的值已经为0,所以可以从1开始计算
for (int i = 1; i <= n; i++)
{
// i的二进制形式中1的个数比i&(i-1)的二进制形式中1的个数多1个
ret[i] = ret[i & (i - 1)] + 1;
}
return ret;
}
上述代码的时间复杂度为O(n)。
方法三:
如果正整数i是偶数,那么i相当于将i/2左移一位的结果,因此偶数i和i/2的二进制形式中1的个数是相同的。
如果正整数i是奇数,那么i相当于将i/2左移一位后,再将最右边一位设为1的结果,因此奇数i的二进制形式中1的个数比i/2的二进制形式中1的个数多1个。
例如,
3:00000000000000000000000000000011
6:00000000000000000000000000000110
7:00000000000000000000000000000111
vector<int> countBits(int n)
{
vector<int> ret(n + 1, 0); // 创建n+1个元素的数组,将所有元素初始化为0
// 0的二进制形式中没有1,现在ret[0]的值已经为0,所以可以从1开始计算
for (int i = 1; i <= n; i++)
{
// ret[i] = ret[i / 2] + (i % 2);
// 当i为非负数时,i/2=i>>1,i%2=i&1,且位运算比除法运算和取余运算效率高
// 代码优化如下:
ret[i] = ret[i >> 1] + (i & 1);
}
return ret;
}
上述代码的时间复杂度为O(n)。
2. 只出现一次的数字(简单)
a ^ a = 0? ? 0 ^ a = a
class Solution {
public:
int singleNumber(vector<int>& nums) {
// 4^1^2^1^2=4^0=4
int n = nums.size();
int ans = 0;
for (int i = 0; i < n; i++)
{
ans ^= nums[i];
}
return ans;
}
};
3. 只出现一次的数字 II(中等)
将数组中只出现一次的元素剔除,剩下的元素都出现了三次,将这些元素的各个数位的值相加,各个数位的和一定是3的倍数。
将数组中所有元素的各个数位的值相加:
- 如果某位的和能被3整除,说明只出现1次的元素该位是0
- 如果某位的和被3除余1,说明只出现1次的元素该位是1
class Solution {
public:
int singleNumber(vector<int>& nums) {
int n = nums.size();
vector<int> bitSums(32, 0); // int型有32位
for (int i = 0; i < n; i++)
{
for (int j = 0; j < 32; j++)
{
bitSums[j] += nums[i] >> (31 - j) & 1;
}
}
int ret = 0;
for (int i = 0; i < 32; i++)
{
ret = (ret << 1) + bitSums[i] % 3;
}
return ret;
}
};
4. 最大单词长度乘积(中等)
用int型整数记录某个字符串中出现的字符。
如果字符串中包含'a',那么整数最右边的数位为1,
如果字符串中包含'b',那么整数倒数第2的数位为1 ……
用flags[i]记录words[i]出现的字符,如果flags[i] & flags[j] == 0,那么words[i]和words[j]没有相同字符,此时可以计算它们长度的乘积,然后和之前计算过的最大值比较。
class Solution {
public:
int maxProduct(vector<string>& words) {
int n = words.size();
vector<int> flags(n, 0);
// 设置flags[i]
for (int i = 0; i < n; i++)
{
for (int j = 0; j < words[i].size(); j++)
{
flags[i] |= 1 << (words[i][j] - 'a');
}
}
// 找没有相同字符的words[i]和words[j]
int ans = 0;
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
if ((flags[i] & flags[j]) == 0)
{
int prod = words[i].size() * words[j].size();
ans = max(ans, prod);
}
}
}
return ans;
}
};
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!