你所不太了解的位运算

前言

与、或、非、异或这些运算符的含义就不过多介绍了,本篇文章主要通过一个例题来介绍如何灵活使用位运算符,来给我们的算法锦上添花。例题如下:
在这里插入图片描述算法代码如下

class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for(int num : nums){
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }
        return ones;
    }
}

解题思路
如下图所示、考虑数字的二进制形式,对于出现三次的数字,各二进制位出现的次数都是3的倍数。因此统计所有数字的各二进制位中1的出现次数,并对3求余,结果则为只出现一次的数字。
在这里插入图片描述

方法一:有限状态自动机

各二进制位的位运算规则相同,因此只需要考虑一位即可。如下图所示,对于所有数字中的某二进制位1的个数,存在3种状态,即对3余数为0,1,2。

  • 若输入二进制位1,则状态按照以下顺序转换;
  • 若输入二进制位0,则状态不变。

在这里插入图片描述
如下图所示,由于二进制只能表示0,1,因此需要使用两个二进制位来表示3个状态。设此两位分别为two,one,则状态转换变为:
在这里插入图片描述
接下来,需要通过状态转换表导出状态转换的计算公式。首先回忆一下位运算特点,对于任意二进制位x,有:

异或运算:x^0=x,x^1=~x
与运算:x&0=0,x&1=x

计算one方法:
设当前状态为two one,此时输入二进制位n。如下图所示,通过对状态表的情况拆分,可推出one的计算方法为:

if(two==0){
  if(n==0)
     one=one;
  if(n==1)
     one=~one;
if(two==1)
    one=0;
}

引入异或运算,可将以上拆分化简为:

if(two==0)
   one=one^n;
if(two==1)
   one=0;

引入与运算,可继续简化为:

one=one^n&~two;

在这里插入图片描述
计算two方法:
由于是先计算one,因此应在新one的基础上计算two。如下图所示,修改为新one后,得到了新的状态图。观察发现,可以使用同样的方法计算two,即:

two=two^n&~one

在这里插入图片描述
返回值:
以上是对数字的二进制中“一位”的分析,而int类型的其他31位具有相同的运算规则,因此可将以上公式直接套用在32位数上。遍历完所有数字后,各二进制位都处于状态00和状态01(取决于“只出现一次的数字”的各二进制位是1还是0),而此两状态是由one来记录的(此两状态下twos恒为0),因此返回ones即可
复杂度分析:

  • 时间复杂度O(N):其中N位数组nums的长度;遍历数组占用O(N),每轮中的常数个位运算操作占用O(3232)=O(1)。
  • 空间复杂度O(1):变量ones,twos使用常数大小的额外空间。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路的苟狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值