算法基础
第一章 位运算
位运算
1 整数原码反码补码
正数:原码=反码=补码
负数:原码=补码的反码+1;补码=原码的反码+1
2 位运算符
位运算是指对转换成二进制的数字进行每一位上的0、1的运算,运算涉及到五种运算:与(&),或(|),异或(^),左移(<<),右移(>>)。
C/C++语言提供了六种位运算符来进行位运算操作:
& 按位与 (双目)
| 按位或 (双目)
^ 按位异或 (双目)
~ 按位非(取反) (单目)
<< 左移 (双目)
>> 右移 (双目)
3 应用
3.1 与&运算
(1)a&1:用于判断奇偶;
(2)a&(1<<(n-1)):判断第n位是0还是1;
(3)x & (x - 1) :用于消去x最后一位的1。用于统计x的二进制数中1的个数等;
(4)x & (-x) :一般是用来获取某个二进制数的 LowBit ,在树状数组中会用到,lowbit(x)是x的二进制表达式中最低位的1所对应的值。
lowbit 的定义是:x & (-x),当x为 0 时结果为 0;x为奇数时,结果为 1;x为偶数时,结果为x中最大的 2 次方因子。
(5)x = x|(1<<i):设置第 i 位为 1
(6)x = x & (~(1<<i)):设置第 i 位为 0
(7)x = x ^ (1<<i):将第 i 位取反
例题:
(1)(Leetcode191) 位1的个数
输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
方法1:普通方法,移位+与运算
while(x!=0)
{
cnt+=(x&1);
x=(x>>1);
}
方法2:使用x&(x-1) 或者x&(-x)
unsigned int x=-1;
int cnt=0;
while(x){
x-=x&(-x);//或者x=x&(x-1);
cnt++;
}
cout<<cnt;
3.2 异或^运算
可以看做不进位加法。异或的性质:
1)0^A=A 0异或任何数A都等于A本身
2)A^A=0 任何数A异或其本身都等于0
3)异或运算满足交换律, 即A^B=B^A
4)异或运算满足结合律,即(A^B)^C=A^(B^C)
例题:
(1)(Leetcode-136)一个数组中,只有1个数出现奇数次,其他数均出现偶数次。求出现奇数次的这个数
int MyFun(std::vector<int>& arr)//vector<int> arr 等价于int arr[]
{
int ret = 0;
for (int it : arr)
{
ret ^= it;
}
return ret;
}
(2)(Leetcode-268)给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
int missingNumber(int[] nums) {
int ans = 0;
for(int i = 0; i < nums.length; i++){
ans = ans ^ (i + 1) ^ nums[i];
}
return ans;
}
4 建议练习
Leetcode网站-位运算
136,137,190,191,231,260,268,287.
5 补充知识bitset专题
在 STL 的头文件中 中定义了模版类 bitset,用来方便地管理一系列的 bit 位的类。bitset 除了可以访问指定下标的 bit 位以外,还可以把它们作为一个整数来进行某些统计。
bitset 模板类需要一个模版参数,用来明确指定含有多少位。
bitset 就相当于一个 只能存储二进制,也就是 0 和 1 的 bool 数组,但是可以直接当作一个数进行左移右移,取或取反等二进制操作。
如果直接用 bool 数组存储二进制每一位的话,n 位存储复杂度为 O(n),但是用 bitset 的话复杂度会减少到 O(n/32)。
所以一般用二进制状态压缩时就会用 bitset。
5.1 定义
对于 bitset 的定义有四种方式:
5.1.1 直接定义,每一位初始为0:
bitset name; // 定义长度为 N 的二进制数组,命名为 name;
bitset<8> b1;//定义8位二进制数。默认都是0
cout << b1 <<endl;
5.1.2 存储数字的二进制:
bitset name(num); // 定义长度为 N 的二进制数组,命名为 name,将数字 num 的二进制存到其中;
bitset<8> b2(12); //二进制长度8,将12转化为二进制存到其中。
cout << b2 << endl;
5.1.3 存储 01字符串 对应的二进制:
bitset name(string); // 定义长度为 N 的二进制数组,命名为 name,将01串 string 存到其中,长度不够前补 0,长度过长截断;
string s = "10010";
bitset<8> b3(s);
cout << b3 << endl;
5.1.4 存储 01字符数组 中的二进制:
bitset name(char[]); // 定义长度为 N 的二进制数组,命名为 name,将 01字符数组存到其中,长度不够前补0,长度过长截断;
char chs[10] = "10010";
bitset<8> b4(chs);
cout << b4 << endl;
【注意】
直接输出 bitset 为正常的二进制,但是遍历所有位置来输出的话就是逆序的:
bitset<8> bs(13);
cout << bs <<endl; //00001101
for(int i=0;i<7;i++) cout<<bs[i]; //10110000
定义 bitset 对象的示例代码:
const int MAXN = 32;
bitset<MAXN> bt; // bt 包括 MAXN 位,下标 0 ~ MAXN - 1,默认初始化为 0
bitset<MAXN> bt1(0xf); // 0xf 表示十六进制数 f,对应二进制 1111,将 bt1 低 4 位初始化为 1
bitset<MAXN> bt2(012); // 012 表示八进制数 12,对应二进制 1010,即将 bt2 低 4 位初始化为 1010
bitset<MAXN> bt3("1010"); // 将 bt3 低 4 位初始化为 1010
bitset<MAXN> bt4(s, pos, n);// 将 01 字符串 s 的 pos 位开始的 n 位初始化 bt4
5.2 常用操作:
5.2.1 像数字一样进行 取或 或者 左移右移
b2 |= b3; //两个二进制数取或操作;
b2 &= b3; //两个二进制数取与操作;
b2 ^= b3; //取异或;
b2 = ~b2; //取反;
b2 <<= 2; //左移右移;
5.2.2 自带函数
bt.any() // bt 中是否存在置为 1 的二进制位?
bt.none() // bt 中不存在置为 1 的二进制位吗?
bt.count() // bt 中置为 1 的二进制位的个数
bt.size() // bt 中二进制位的个数
bt[pos] // 访问 bt 中在 pos 处的二进制位
bt.test(pos) // bt 中在 pos 处的二进制位是否为 1
bt.set() // 把 bt 中所有二进制位都置为 1
bt.set(pos) // 把 bt 中在 pos 处的二进制位置为 1
bt.reset() // 把 bt 中所有二进制位都置为 0
bt.reset(pos) // 把 bt 中在pos处的二进制位置为0
bt.flip() // 把 bt 中所有二进制位逐位取反
bt.flip(pos) // 把 bt 中在 pos 处的二进制位取反
bt[pos].flip() // 同上
bt.to_ulong() // 用 bt 中同样的二进制位返回一个 unsigned long 值
os << bt // 把 bt 中的位集输出到 os 流
int cnt_1 = b2.count(); //查询二进制数组中1的个数;
int len = b2.size(); //二进制数组的长度,就是定义的长度;
int test = b2.test(7); //判断第x个位置是0还是1,也就是输出第x个位置,注意逆序;
b2.flip(); //将二进制每一位取反;
b2.flip(3); //将二进制第x位取反;
b2.set(); //将二进制每一位置为1; reset置为0;
b2.set(3); //将第x个位置置为1;
string ss = b2.to_string(); //将二进制数组转化为字符串。