一种通用的SingleNumber问题解法

题目:

给一个integer数组,每个元素出现3次,除了某个元素,找只出现一次的元素。

对于这个问题,我们希望元素出现了k次(这里为3),就变成0。那么剩下的,就是我们要找的元素。

所以,我们的目标就是,如何能让某个元素累计出现了k次,就变成0。

首先要明白一点,integer在计算机中是以bit的形式存储的,所以我们可以应用位运算操作。

为了简化问题,假设array中的元素只有一位(数组中的值要么是0,要么是1),我们初级目标是来统计1出现的次数,当1出现了k次,计数器的值为0(跟题目出现了k次就置0对应)。

为了统计1次数的次数,我们需要一个计数器,计数器假设有m位(xm,...,x1)。(x1为最低位)

对于这个计数器:

1.初始状态为0。

2.当碰到 0 时,计数器的值不变。

3.当碰到 1 时,计数器的值+1。

4. 计数器的位数 m > logk。

1. 问题:如何让这个计数器实现2,3功能。

假设我们的计数器有3位。

初始状态为:

x3  x2  x1

0    0    0

在这个状态下,当我们碰到0 的时候,计数器不变;碰到1的时候,计数器要变成1,可以通过 x = x ^ i 实现。(^为异或符号)

碰到1之后状态变为:

x3  x2 x1 

0   0    1

再碰到0的时候,x = x^i 依旧满足要求,但碰到1的时候,x1 = x1 ^ 1 = 0, 但此时要发生进位操作了(x2要变成1)

那x2在什么情况要变成1呢,当且仅当x1 = 1, 且i = 1的时候。

再考虑,x3什么时候要变化呢,当且x2 = 1, x1 = 1, 且i = 1的时候。

所以可以归纳出:

xm = xm ^ (xm-1 & ... & x1 & i);  

xm-1 = xm-1 ^(xm-2 & ... & x1 & i);

...

x1 = x1 ^ i;

此时,计数器可以实现了2,3要求了。

2. 问题:现在实现的这个计数器,只能当k = 2的m次的时候,才会置0。

比如且当m = 3 的时候,只有统计1 出现8次的时候,计数器的值才可以变成0 。

如何让k < 2的m次就置0呢?

所以,如何有一个cut操作,当计数器的值累计到了k, 就重新置0 呢?

所以,我们需要一个步骤,将xm,...,x1 与一些变量(称为mask),进行and 操作。

xm = xm & mask, ..., x1 = x1 & mask.

我们需要这个mask,当 计数器的值为k的时候,进行位运算时候,mask为0(这样进行and,计数器的所有位就变成0了)

当计数器的值不为k的时候,mask为1(这样进行and,计数器的所有位保持不变)

mask可以这样构造:

k 的二进制表示为: km,..., k1.

mask = ~(x1' & x2' & ... xm'), where xj' = xj if kj = 1 and xj' = ~xj if kj = 0 (j = 1 to m).

比如:k = 5: k1 = 1, k2 = 0, k3 = 1, mask =  ~(x1 & ~x2 & x3);

假设此时计数器状态为这样:

x3  x2  x1 

1    0    0

~(x1 & ~ x2 & x3) = 1, 所以,xi & mask 后,都不会发生变化

假设此时计数器状态为:

x3  x2  x1:

1    0    1

~(x1 & ~ x2 & x3) = 0,所以, xi & mask后,就都置0了。

而且:当且仅当计数器状态为101的时候,才置0。

现在,可以让它提前置0 了。

3. 扩展: 从 1 位 到 32位

当位数变多了,我们需要为每一位创建一个计数器吗?

答案是不需要的。

通过使用m 个 32位 的integer 取代 32个 m 位的integer, 能够联合进行位计数。(因为不同bit上的操作都是相互独立的)

现在的计数器长这样。

     bit32  bit31 ...      bit 1

x3

x2

x1

4. 最后:重复k次的消掉了,但从哪里获得那个single number 呢?

假设我们的single number 是 1 0 0

别的元素出现k次,single number出现p次。

当p = 1的时候:

最后我们的计数器长这样:

          bit32  bit31 ...    bit3 bit2   bit1 

x3

x2

x1     0      0     ...      1  0      0

当p = 2 的时候:

我们的计数器长这样:(x3为高位,相当于 100 + 100)

          bit32  bit31 ...    bit3 bit2   bit1 

x3

x2                             1   0     0 

x1     0      0     ...      0  0      0

当p = 3 的时候:

          bit32  bit31 ...    bit3 bit2   bit1 

x3

x2                             1   0     0 

x1     0      0     ...      1   0      0

当p = 4 的时候:

          bit32  bit31 ...    bit3 bit2   bit1 

x3                             1   0      0

x2                             0   0      0 

x1     0      0     ...      0   0      0

所以?归纳出该从哪里找single number了吧?

将single number出现的次数p 转成二进制形式,假设为pm, .. p1

假设pi = 1, 那么 xi 就是single number的二进制表示,返回xi即可。

 此算法的时间复杂度为O(n*logk) ,空间复杂度为 O(logk) 

代码:

    public int singleNumber(int[] nums) {
        return singleNumber(nums, 3, 1);
    }

    // all elements appears k times except one appears p times, find that one
    public int singleNumber(int[] nums, int k, int p) {
        int m = bit_length(k);
        int[] counter = new int[m];
        int mask = 1;
        for (int n : nums) {
            // counting
            for (int index = m - 1; index >= 0; index--) {
                int sum = n;
                for (int innerIndex = index - 1; innerIndex >= 0; innerIndex--) {
                    sum &= counter[innerIndex];
                }
                counter[index] ^= sum;
            }
            //calculating mask
            mask = ~0;  // pay attention here!
            int nowK = k;
            for (int i = 0; i < m; i++) {
                if ((nowK & 1) == 1) {
                    mask &= counter[i];
                } else {
                    mask &= ~counter[i];
                }
                nowK >>= 1;
            }
            mask = ~mask;
            for (int i = 0; i < counter.length; i++) {
                counter[i] &= mask;
            }
        }
        // choose return
        p %= k;
        for (int i = 0; i < m; i++) {
            if ((p & 1) == 1) {
                return counter[i];
            } else {
                p >>= 1;
            }
        }
        return -1;
    }
    public int bit_length(int bits ) {
        int res = 0;
        while (bits > 0) {
            bits >>= 1;
            res++;
        }
        return res;
    }


参考:

https://leetcode.com/discuss/31595/detailed-explanation-generalization-bitwise-operation-numbers







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值