关于Single Number II的一些讨论

来源:互联网 发布:iphone主题软件下载 编辑:程序博客网 时间:2024/06/06 07:42

关于 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类型的,我们虽然一开始并没有将所有位都考虑进来,那是因为所有位的操作是并行的。但不考虑并不代表不存在,它就是我们要的结果。

0 0