位操作,找出一个数组中某个出现p次的数

题目:一个数组中一个数出现了p次,其余数出现了k次(p%k != 0),不用额外空间找出这一个数。


由于在电脑中,数是由位存储的,我们先考虑简单情况----只有一位的数(0或者1),我们对1进行计数,当计数器到达k时,归零, 设我们扫描数组遇到的数为i。假设这个计数器是一个m位的二进制数:xm, ..., x1。这个计数器有以下性质。

 

使用 x = x ^ 0 可以保证遇0计数器不变;

  1. 遇0不变,遇1增1
  2. 为了能计数到k,需要满足 2^m >= k

对于1,第一次遇到时, x1 = x1 ^ i , 计数器变成 xm = 0, ..., x2 = 0, x1 = 1;

第二次遇到1时,我们希望x2变成1, x1变成 0, 即为xm = 0,  ... x2 = 1, x1 = 0 。对于x1来说仍然使用 x1 = x1 ^ i来改变状态,  对于x2来说只有x1和遇到的数同时为1时,才需要改变状态,所以使用x2 = x2 ^ (x1 & i); 同理对于xm, xm = xm ^ (xm-1 & ... & x1 & i)。

对于计数器,当2^m == k时,计数k个之后会自动归0;

当2^m > k时,我们自己设计让计数器计数k个之后回归0,为了实现这个效果,我们把计数器与mask做&操作,mask具有以下性质

  • 只有当计数器到达k时,mask才为0,其余时刻为1.

把k写成m位的二进制数, km, ...,k2, k1, 我们的计数器为xm, ..., x2, x1, 构造mask如下

mask = ~(y1 & y2 & ... & ym), where yj = xj if kj = 1, and yj = ~xj if kj = 0 (j = 1 to m).

 上面的mask保证了只有km == xm, ..., k1 == x1,即计数器等于k时,mask为0。例如:

k = 3时 2^m >= k, 所以 m = 2,  k1 = 1, k2 = 1, mask = ~(x1 & x2)
k = 5: k1 = 1, k2 = 0, k3 = 1, mask = ~(x1 & ~x2 & x3);

对于位数为1的数,算法表达如下:

for (int i : nums) {
    xm ^= (xm-1 & ... & x1 & i);
    xm-1 ^= (xm-2 & ... & x1 & i);
    .....
    x1 ^= i;
    
    mask = ~(y1 & y2 & ... & ym) where yj = xj if kj = 1, and yj = ~xj if kj = 0 (j = 1 to m).

    xm &= mask;
    ......
    x1 &= mask;
}

接下来讨论普遍情况,遇到的数是32位。那么我们就需要为32位构造32个计数器,我们通过m各32位的数就构成了32个m位的计数器。由于位操作是独立的,相互之间无影响,即32个计数器之间是独立的,所以这样构造是合法的。通过第r个计数器去对数的第r位的1进行计数。

最后要讨论的是,计数结束之后,怎么得到寻找的数。首先明确这m个数xm, ... x1的概念,xm, ... x1的第r位由所扫描数组中的数的第r位决定。注意到计数器到k会归0,那么有效的次数就是 p' = p % k。

将p'写成m位的二进制数, pm, ..., p1, 注意p'等于每一个counter。 结论是:如果pj == 1, 那么xj就是所寻找的数(xj是上图横着的32位的数)

证明:如果xj的第r位为1,那么所寻找的数第r位一定为1。 如果xj的第r位为0,如果所寻找的数第r位为1,由于pj = 1, 所以

xj的第r位也一定为1,矛盾;所以所寻找数的第r位为为0;综上当pj =1时, xj 等于所寻找的数。

注意:当pj = 0 时,当所寻找数第r位为1, xj的第r位为0, 当所寻找数的第r位为0时,xj的第r位为0, 所以当pj=0时, xj =0, 所以最后的答案也可以写成 x1 | x2 |... | xm


举例,k = 2, p = 1, 这时m = 1,需要一个计数器;2^m == k,计数器会自动归0,不需要mask

public int singleNumber(int[] nums) {
        int x1 = 0;
         
        for (int i : nums) {
            x1 ^= i;
        }
         
        return x1;
    }

k= 3, p = 1, 这时 m = 2, 需要两个计数器x2, x1; 2^m > k,需要mask,k2 = 1, k1 = 1, 所以 mask = ~(x1 & x2)

public int singleNumber(int[] nums) {
        int x1 = 0, x2 = 0, mask = 0;
         
        for (int i : nums) {
            x2 ^= x1 & i;
            x1 ^= i;
            mask = ~(x1 & x2);
            x2 &= mask;
            x1 &= mask;
        }

        return x1; 
    }

本文参考,英文好的可以直接看英文:https://leetcode.com/problems/single-number-ii/discuss/43295/Detailed-explanation-and-generalization-of-the-bitwise-operation-method-for-single-numbers

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值