关于Single Number II的一些讨论

关于 Single Number II 这一题,我在讨论区中发现了这样一段代码,简直让人眼前一亮。但是关于思路,作者却没有多说一句话,只留下两眼放空的我。

讨论地址:challenge-me-thx
作者:againest1
代码:

public int singleNumber(int[] A) {
    int ones = 0, twos = 0;
    for(int i = 0; i < A.length; i++){
        ones = (ones ^ A[i]) & ~twos;
        twos = (twos ^ A[i]) & ~ones;
    }
    return ones;
}

回过神来,我在帖子下面发现了有两位热心群众对这段代码进行了解释,读了几遍后,终于理解了一二,在此翻译下来。

解释A
- 作者:woshidaishu
- 地址:https://discuss.leetcode.com/topic/2031/challenge-me-thx/17
- 翻译如下:
这段代码乍一看有点复杂,理解起来也很困难。
然而,如果你从布尔代数 的角度去考虑这个问题,事情将变得清晰明了。

对于仅出现一次的数的每一个bit我们需要怎们存储?因为32bit中的每一位都遵守相同的规则,我们只需要考虑1bit。我们知道一个数最多出现3次,所以我们需要用2个bit位去存储他们。现在我们有四个状态,00,01,10和11,但是我们只需要3个就够了。

在这个解决方法中,00,01和10被选中。用ones 表示第一个bit位,twos 表示第二个bit位。然后我们需要对onestows 设置规则,使它们按照我们的意愿进行演算。这个完整的循环是00->10->01->00(0->1->2->3/0)。

  • 对于ones,我们使ones = ones ^ A[i]; if(twos == 1) then ones = 0,以上可以翻译这种形式ones = (ones ^ A[i]) & ~twos

  • 相似的,对于twos,我们使twos = twos ^ A[i]; if(ones* == 1) then twos = 0,等价于twos = (twos ^ A[i]) &~ones。注意:ones*ones 计算后的值,这也是为什么twos 被后计算。

这有另一个例子,如果一个数至多出现了5次,我们可以使用同样的方法写一个程序。现在我们需要用3个bit位,循环状态是000->100->010->110->001。代码如下:

int singleNumber(int A[], int n) {
    int na = 0, nb = 0, nc = 0;
    for(int i = 0; i < n; i++){
        nb = nb ^ (A[i] & na);
        na = (na ^ A[i]) & ~nc;
        nc = nc ^ (A[i] & ~na & ~nb);
    }
    return na & ~nb & ~nc;
}

或:

int singleNumber(int A[], int n) {
    int twos = 0xffffffff, threes = 0xffffffff, ones = 0;
    for(int i = 0; i < n; i++){
        threes = threes ^ (A[i] & twos);
        twos = (twos ^ A[i]) & ~ones;
        ones = ones ^ (A[i] & ~twos & ~threes);
    }
    return ones;
}

我希望以上的内容能帮助你更好的理解这个问题。

解释B
- 作者:oflucas
- 地址:https://discuss.leetcode.com/topic/2031/challenge-me-thx/77
- 翻译如下:

让我来更加清楚的解释这个原理:

X=***1***1***
Z=***0***0***
X=***1***1***
Y=***0***1***
X=***1***1***
Z=***0***0***
Z=***0***0***
     P   Q

X,Y,Z是3个数,并在P、Q位置显示了它们的bit值。

如果你可以计算每一个位置的bit和,接下来重要的事就是计算sum % 3。如果一个特殊的数只出现了一次,我们可以通过直接计算sum % 3 来得到那个位的bit值。

例如:

  • 将Q位置的所有bit值加起来得到4, 之后4 % 3 = 1,你得到数Y在Q位置的bit值。
  • 将P位置的所有bit值加起来得到3,之后3 % 3 = 0,你得到数Y在P位置的bit值。

当你在计算sum 时,数的次序需要考虑吗?不,我们只需要关心sum,尤其是sum % 3

当你add 每一位bit值时,仔细观察sum,每当它加1时,它会有如下循环状态:0->1->2->0。以二进制表示:00->01->10->00。

我们针对每一个bit位模拟这个循环过程吗?我为什么要那么做?稍后我将告诉你。

让我们来通过设计一个bit操作<add> 来模拟上述过程:

假设b1b0 代表sum的二进制形式下的bit位:

我想设计的<add> 以至于当b1b0 加 ‘1’时,有如下表规律:

00 <add> 1 = 01, means 0 + 1 = 1.

01 <add> 1 = 10, means 1 + 1 = 2.

10 <add> 1 = 00, means 2 + 1 = 0, because it has % function embedded.

所以敲敲你的脑袋来理解<add> 操作:

比如说你<add> 一个比特r,这个操作定义如下:

b0 = b0 xor r & ~b1;

b1 = b1 xor r & ~b0;

你将发现,这个关于bit的操作完成了<add> 的功能。

所以当你有n个SINGLE BIT数,除了一个数出现了1次,其它的数重复出现3次。你能找到这个特殊的数吗?

Lets do it:

b0 = b1 = 0;
For (r : nums) { b0b1 <add> r };
return b0;

事实上,bit操作并行在每一个bit位。也就是说对于位置 P 和 Q 和 其他位置的操作是并行的。因此代码如下:

public int singleNumber(int[] A) {
    int b0 = 0, b1 = 0;
    for(int i = 0; i < A.length; i++){
        b0 = (ones ^ A[i]) & ~b1;
        b1 = (twos ^ A[i]) & ~b0;
    }
    return b0;
}

如果你想返回一个只出现2次的数,return b1

感谢againest1 的先驱工作。


1.读到这里,或许你还是未知一二。或许正在想,我们要讨论的数可不仅仅是2个bit而已啊,解释中的代码仅仅用b1b0或onestwos就能达到目的了吗?

我们以32位int为例,当我们进行位运算时,每一位都参与了运算。所以我们没必要去管其它位,因为我们心里清楚,他们是一根绳上的蚂蚱。我们只需要关注2位就够了,因为2位足以表示出3种情况。

2.结果返回b0或ones就行了吗?我们要的可是一个数,可不是1个bit啊。真的可以吗?

要记得,b0是int类型的,我们虽然一开始并没有将所有位都考虑进来,那是因为所有位的操作是并行的。但不考虑并不代表不存在,它就是我们要的结果。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值