位运算理论篇

位运算是针对整数二进制形式直接操作的高效运算,包括按位与、或、异或、取反、左移和右移。本文介绍了位运算的运算律、优先级,以及它们在获取特定二进制位、消去位、判断2的次幂、计数、转换和枚举等场景中的应用。此外,还展示了如何利用位运算解决找到序列中唯一出现一次的数的问题。
摘要由CSDN通过智能技术生成

位运算理论篇

位运算概述

计算机中的数在内存中都是以二进制形式进行存储的 ,而位运算就是直接对整数在内存中的二进制位进行操作,执行效率非常高,在程序中使用位运算进行操作,会大大提高程序的性能。

位运算符

名称符号描述
按位与&如果两个数相应的二进制位为1,则该位的结果值为1,否则为0
按位或|两个数相应的二进制位中只要有一个为1,该位的结果值为1
按位亦或^两个二进制位相同则为0,否则为1
按位取反~用来对一个二进制数按位取反,即将0变1,将1
左移<<x<<n代表将一个数x的各二进制位全部左移n位,右补0,比如1<<3的值为8,因为它的二进制从0001,变成了1000
右移>>x>>n代表将一个数x的各二进制位全部右移n位,左补0,如8>>3就是1,二进制从1000变成了0001

位运算符的优先级

可参考运算符优先级表

位运算符的运算律

名称运算式
交换律A&B==B&A,AB==BA
结合律(同运算符下)ABCA(BC),A&B&CA&(B&C)
同一律A|0==A,A&1=A
幂等律A^AA,A&AA
零律A|1==0
互补律A|A=-1,A&A=0

位运算符的应用

常见应用

应用原理
获取第k个二进制位(从0开始):n>>k&1例如:二进制100100>>21001,1001&11001&0001==1
消去数x上的最后面一位1:x&(x-1)。分析x-1的两种情况,需要借位和不需要借位:不需要借位时,x-1的最后一位是0,x的最后一位是1,&运算后得到x-1,除了最后一位变为0其他完全相同;需要借位时,前一位如果是0,继续往前面借位,最终最后一位1被借位借走变为0,例如1000000变成0111112,减去1后变为0111111,&操作后全部为0。同样的,我们也可以消去最后一个0,只需使用x&(x+1)
判断一个数是否是2的次幂:判断x&(x-1)是否为0如果一个数是2的次幂,那么它的二进制位只有一个1,我们把这个1消去,判断是否为0
判断一个整数二进制位有多少个1:1.使用n>>k&1逐个判断。2.使用x&(x-1)逐个消去二进制位上有多少个1,我们可以逐个判断它的最后一个二进制位到第一个二进制位是否为1,也可以通过不断消去最后一位1并记录进行多少次后该为0。同理,用来判断多少个0也是可以的
将整数x转换为y,需要改变多少个二进制位:判断x|y的二进制位中1的个数将整数x转换为y,如果x和y在第i位上相等,则不需要改变这一位,如果在第i位上不相等,则需要改变这一位。所以问题转化为了x和y有多少个二进制位不相同。位运算中存在异或操作,相同为0,相异为1,所以问题转变成了计算x异或y之后这个数中1的个数。
返回x的最后一位1:x&-x或者x&(~x+1)例如:1000100返回成为100例如一个二级制串:……10000,用~取反后变为……01111,再加一后变为……10000,此时再进行&操作,因为x的最后一位1前面是所有的二进制位都已经取反了,所以&操作得到的结果为00000……10000
二进制进行枚举例如S = [1,2,3],我们需要得到[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2] ],那么我们可以用二进制表示他们之间的枚举关系:此处有3个元素,我们可以选择000,001,010,011,100,101,111这些二进制,第n个二进制位上为1,我们就选择第n个元素,如101选择1和3,000不选择任何元素
一个序列中,只有一个数出现一次,剩下都出现两次,找出出现一次的。因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或起来,就可以得到唯一的那个数。
一个序列中,只有一个数出现一次,剩下都出现三次,找出出现一次的。因为数是出现三次的,也就是说,只要我们把所有数第n位上二进制取出来相加求和,再对三取余,最终得到的一定是只出现一次数第n位上的二进制位

最后一个应用给出两种样例代码

int singleNumber(vector<int>& nums) {
	int ans = 0;
	int help[32]={0};//存储i位上的2进制之和
	for(int i=0;i<nums.size();i++)
		for (int j = 0; j <= 31; j++)
		{
			help[j] += ((nums[i] >> j)&1);
		}
	for (int i = 0; i <= 31; i++)
		ans |= ((help[i] % 3)<<i);
	return ans;
    }
int singleNumber(vector<int>& nums) {
        int ans = 0;
        for (int i = 0; i < 32; ++i) {
            int total = 0;
            for (int num: nums) {
                total += ((num >> i) & 1);
            }
            if (total % 3) {
                ans |= (1 << i);
            }
        }
        return ans;
    }

位运算表示一些常规运算

  • 交换两数:
void swap(int &a,int &b){
      a ^= b;
      b ^= a;
      a ^= b;
  }
  • 实现乘以2和除以2:a<<1a*2,a>>1a/2
  • 位运算判断奇偶性:在二进制中,最低位决定了是奇数还是偶数,所以我们可以提取出最低位的值,也就是判断a&1是1还是0,是0则是偶数,是1则是奇数
  • 改变正负:-x=~x+1,关于详细原因可以参考原码反码和补码的知识
  • 实现加法A+B:我们可以使用异或操作来实现,因为异或操作其实是不进位加法。那么进位操作我们就可以通过A&B来实现,因为A&B得到的都是A和B上都有的1,我们左移后得到的就是进位之后的结果
int aplusb(int a, int b) {
        while(b){
            int c = a ^ b;
            int d = (a & b) << 1;
            a = c;
            b = d;
        }
        return a;
    }
  • 一个序列中,只有两个数出现一次,剩下都出现两次,找出出现一次的

这题我们可以先排序再判重,判重使用双指针时间复杂度为n,排序时间复杂度为nlgn,最终时间复杂度为nlgn,空间lgn,因为要使用栈空间排序。

当然我们也可以用哈希表存储,遍历一次存入哈希表,选出只出现一次的就行了,时间复杂度n,空间复杂度nlgn。

但是我们还可以使用位运算,它的时间复杂度也是n,我们甚至可以把空间复杂度降低到1.

时间复杂度n,空间复杂度n

假设出现一个的两个元素是x,y,那么最终所有的元素异或的结果就是res = x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1,那么这一位1对于这两个数x,y只有一个数的该位置是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。求出x,y其中一个,我们就能求出两个了。

代码如下:

   vector<int> singleNumber(vector<int>& nums) {
        vector<int>a,b,ans(2,0);
        int n,res=nums[0];
        for(int i=1;i<nums.size();i++)
        res^=nums[i];
        for(n=0;n<=31;n++)
        {
            if(res>>n&1)
            break;
        }
        for(int i=0;i<nums.size();i++)
        if(nums[i]>>n&1)
        a.push_back(nums[i]);
        else
        b.push_back(nums[i]);
        ans[0]=a[0];
        ans[1]=b[0];
        for(int i=1;i<a.size();i++)
        ans[0]^=a[i];
        for(int i=1;i<b.size();i++)
        ans[1]^=b[i];
        return ans;
    }

时间复杂度n,空间复杂度1

整体思路差不多,但是利用了0^x=x的运算律,避免开辟空间存储

 vector<int> singleNumber(vector<int>& nums) {
        vector<int>ans(2,0);
        int n,res=nums[0];
        for(int i=1;i<nums.size();i++)
        res^=nums[i];
        for(n=0;n<=31;n++)
        {
            if(res>>n&1)
            break;
        }
        for(int i=0;i<nums.size();i++)
        if(nums[i]>>n&1)
        ans[0]^=nums[i];
        else
        ans[1]^=nums[i];
        return ans;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值