二进制与位运算
一、二进制基础
1.有符号整数在计算机中的表示–补码
假设正数N的补码为:00001;
那么-N的补码是多少?
答:对正数的补码减一取反:11111;
那么考虑互逆:对-N 的补码取反加一,得到N的补码;
嘿嘿,是不是很神奇,更神奇的是取反加一和减一取反是一样的!!!所以任意一个数取反加一得到它的相反数
但是!!!,最小的负数是特例,例如4位二进制,对-8取反加一不是8,而是-8它本身!!!
2.c++中输出二进制数
cout<<bitset<?>(x)<<endl;
?:指定输出多少位。
二、二进制如此设计的原因
补码的加法运算符合算数加法逻辑。
例如:-5的补码 + 2的补码 == -3的补码
神奇吧,其实上述逻辑的证明可以用数学证明,但笔者能力有限,不做阐释。
三、左移右移
逻辑移位
针对无符号数,整体移动,用0补齐。
算术移位
针对有符号数,符号位不变,对数值位移动,用符号位补齐。
四、位运算骚操作
一、异或运算
1.性质
(1)可以理解为无进位相加。
(2)满足交换律、结合律
(3)0 ^ n = n,n^n=0
(4)整体异或和如果是x,整体中某个部分的异或和如果是y,那么剩下部分的异或和是x^y.
2.交换两个数
有两个数a=1,b=10;
进行如下操作:
a=a^b
b=a^b
a=a^b
则a=10,b=1;
3.返回两个数最大值
4.找到缺失的数字
见leetcode对应题目https://leetcode.cn/problems/missing-number/
class Solution {
public:
int missingNumber(vector<int>& nums) {
int eorall=0,eorhas=0;
for(int i=0;i<nums.size();i++){
eorall^=i;
eorhas^=nums[i];
}
eorall^=nums.size();
return eorall ^ eorhas;
}
};
5.找唯一的出现奇数次的数
出现偶数次的数异或完是0,所以所有数异或得到的就是那个数
见leetcode:https://leetcode.cn/problems/single-number/description/
6.找唯二的出现奇数次的数
先介绍lowbit运算:a&((~a)+1)得到a最右侧1的值;
下面设这两个数是a和b
所有数异或的值eor1=a^b,eor1的最右侧1设为第n位,且a和b的第n位一定不相同。
所以把所有数根据二进制第n位分为两类,分别异或就得到了两个数a,b
见leetcode:https://leetcode.cn/problems/single-number-iii/description/
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
long eor1=0,eor2=0;
vector<int>ans(2);
for(int i=0;i<nums.size();i++){
eor1^=nums[i];
}
long k=eor1&(-eor1);
for(int i=0;i<nums.size();i++){
if(k & nums[i]){
eor2^=nums[i];
}
}
ans[0]=eor2;
ans[1]=eor2^eor1;
return ans;
}
};
二、其他
1.找唯一的出现次数少于m的数
针对0~31位每一位,统计在数组中出现的次数,若某位的次数不是m的倍数,则说明要找的数的这一位为1,否则为0
见leetcode:https://leetcode.cn/problems/single-number-ii/
class Solution {
public:
int singleNumber(vector<int>& nums) {
int m=3,ans=0;
int arr[32];
for(int i=0;i<nums.size();i++){
for(int j=0;j<32;j++){
arr[j]+=(nums[i]>>j)&1;
}
}
for(int i=0;i<32;i++){
if(arr[i]%m!=0)ans|=(1<<i);
}
return ans;
}
};
2.判断是不是2的幂
运用lowbit算法,那么算法只有一行
return n>0&&n==n&(-n);
3.连续范围内所有数字&的结果
1.lowbit算法
https://leetcode.cn/problems/bitwise-and-of-numbers-range/
class Solution {
public:
int rangeBitwiseAnd(int left, int right) {
while(left<right)right-=right&(-right);
return right;
}
};
2.brian kernighan算法(其实一样)
详情见https://zhuanlan.zhihu.com/p/498119781
right-=right&(-right)等价于right=right&(right-1)
4.将二进制逆序
题目链接:https://leetcode.cn/problems/reverse-bits/
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
n =((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);
n =((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);
n =((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);
n =((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);
n =((n & 0xffff0000) >> 16)| ((n & 0x0000ffff) << 16);
return n;
}
};
五、位图
一串bit数组,可以表示一个范围,1个bit代表一个数字,前提是连续范围。
例如:四个int整数就可以存储0~127这128个数字。
六、位运算实现加减乘除
1.首先用位运算实现加法:例如算a+b;
int add(int a,int b){
int ans=a;
while(b!=0){
ans=a^b;
b=(a&b)<<1;
a=ans;
return ans;
}
2.用加法实现减法
a-b=a+(~b+1);
3.实现乘法
int multiply(int a,int b){
int ans=0;
while(b!=0){
if((b&1)!=0){
ans+=a;
}
a <<=1;
b >>=1;
}
return ans;
}
4.实现除法
int neg(int x) {
return ~x + 1;
}
int div(int a, int b) {
//a,b转为正数
int x = a < 0 ? neg(a) : a;
int y = b < 0 ? neg(b) : b;
int ans = 0;
for (int i = 30; i >= 0; i--) {
if ((x >> i) >= y) {
ans |= (1 << i);
x = x - (y << i);
}
}
return a < 0 ^ b < 0 ? neg(ans) : ans;
}