位运算之美

本文探讨了位运算如何通过异或操作高效地解决编程问题,如在给定数组中找出仅出现一次的两个数,同时保持时间复杂度为O(n)和空间复杂度为O(1)。通过实例和技巧,如判断奇偶性、变量交换和子集枚举,展示了位运算在简化算法中的强大作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

计算机底层用的是机器码,即0和1交织而成。位运算在某些情况下,有着神奇的功效,能够提升效率,节省空间。此文对位运算的经典操作进行一些总结。

  • 例如:

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

可以采用异或操作得出两个不同数字的异或值(其他所有成对的数字异或后等于0),最终的结果每一位上若为1则表示两数在此位上不同,反之为0则相同。随便取为1的一位(由于俩数不同,必存在为1的位),将整个数组分为两组(以此位为0和一分别分两组),这样两个不同的数必然会分到两个不同的组中。最后两个不同的组中组内依次异或,可以计算出不同的那一位,两组得到最终两个不同的数。

class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        x = 0
        for i in range(len(nums)):
            x ^= nums[i] # 求不同俩数异或结果
        t = x - (x&(x-1)) # 取最低位1,树状数组的典型操作,位运算优先级较低加括号
        ans = [0,0]
        for x in nums: # 分组并异或
            if t&x:
                ans[0] ^= x 
            else:
                ans[1] ^= x
        return ans

详解

  • 使用位运算求奇数偶数:
x&1==1	x&1==0

最低位为一为奇数,为零为偶数。用此方法的效率要高于一般的求余函数等操作。

  • 使用位运算来交换变量:
a ^= b;
b ^= a;
a ^= b;

第一步:a ^= b ---> a = (a^b);

第二步:b ^= a ---> b = b^(a^b) ---> b = (b^b)^a = a

第三步:a ^= b ---> a = (a^b)^a = (a^a)^b = b

比使用临时变量更高效率。

  • 交换符号将正数变成负数,负数变成正数:
~x+1

整数取反加1,正好变成其对应的负数(补码表示);负数取反加一,则变为其原码,即正数.

  • 消去最低位1:
x&(x-1)

如果是x-(x&(x-1)则为最取最低位1,这是树状数组中典型的lowbit操作的根本依据。

  • 使用二进制进行子集枚举:
arr = [3,5,7]
编号进制子集
0000[]
1001[3]
2010[5]
3011[3,5]
4100[7]
5101[3,7]
6110[5,7]
7111[3,5,7]

每一位代表当前数的取或者不取。

  • 找出现一次的数:

在一个数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的数。此时将每个数依次异或一遍,出现次数为两次的(偶数次均可),会相互抵消为0,剩下的就是只出现一次的数。


Bye

合理运用位运算可以有出其不意的效果,如开头提到的例子,如果用哈希表的话需要花费很多额外的空间,而使用位运算则只需常数复杂度就可以解决这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值