二进制与位运算


一、二进制基础

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;
}
  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值