Single Number II

https://oj.leetcode.com/proble…,承前,http://zrj.me/archives/1344,这个题目是每个数字出现三次,要求找出只出现一次的数字:

Given an array of integers, every element appears three times except for one. Find that single one.

Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

这个很显然,之前的那种异或就不能奏效了,想了想,心思在 3 这个特点上打起了主意。

想法是这样的,假设那些出现 3 次的数为 X1, X2, X3 …,那个特殊的只出现一次的为 Y,那么有

sum = 3(X1 + X2 + X3 + …) + Y

然后,我们可以拿着 sum,遍历数组,尝试用 sum 分别去减每一个数,如果减完发现能 % 3 == 0,那么就说明刚刚减去的那个是 Y

但是,这个简单的思路其实有很多问题:

  1. 如果 Y 本身刚好也 % 3 == 0 怎么办
  2. 就算 Y % 3 != 0,如果 X 其中的一个 Xn % 3 == Y % 3,那么上述的思路也要歇菜

其实第一个问题还可以修修补补,如果我们发现加出来的 sum % 3 == 0,那么就说明 Y % 3 == 0,这样的话,可以给每个数都 + 1,就转换成了能处理的情况,但是第二个问题就没办法了,怎么都绕不过去,所以这个思路,最终是失败的。

想了很久想不到解法,最后还是看了答案,答案在这里,https://github.com/soulmachine…,思路是这样的:

在我们处理出现两次的问题的时候,其实相当于是用了一个记录器,然后把每个数都往这个记录器里面装,一个数第一次进去的时候,会把记录器对应的位置 1,第二次再进去,会把记录器的对应位置再加 1,而刚好二进制,加两次就归零了,所以,最后就能得到那个只出现一次的。

那么,可以想到,如果我们的记录器是三进制的,就可以达到跟先前二进制的时候类似的效果来处理出现三次的数字了。

代码如下

class Solution {
public:
    int singleNumber(int A[], int n) {
        int one(0), two(0), three(0);
        for (int i = 0; i < n; i++) {
            two |= (one & A[i]);
            one ^= A[i];
            three = ~(one & two);
            one &= three;
            two &= three;
        }
        return one;
    }
};

——————————

2014-11-21 09:47:10 udpate

如果觉得上述的位运算技巧性过强,理解不了的话,其实可以这样换个方法来搞:我们准备 32 个 int 整形,每个整形记录了某一位上,出现 1 的累计次数,例如,我们遇到一个 3,就在 a[0] 和 a[1] 都 ++ 而如果遇到一个 4,就在 a[2] 上累加 1,这样,在全部的数字遍历一次之后,我们就可以把这个 a 数组的值都 % 3,得到的,就是剩下的值了,这个方法跟上述的模拟三进制是本质一样的,但是好理解很多,缺点是写起来会比较长。

这个思路尚未经过实际代码验证。

Leave a Reply

Your email address will not be published. Required fields are marked *