数组中最出现一次的元素

来源:互联网 发布:及时生成字幕软件 编辑:程序博客网 时间:2024/06/07 05:53

问题:给定一个数组 a0,a1,a2,...an ,其中只有两个元素出现一次,其它的元素都出现两次,找出这两个元素是多少。要求时间复杂度为 n,空间复杂度为 1。

这个问题可以快速排序(就地排序,不用额外空间)再遍历。但这样的时间复杂度为 n*lgn,不满足要求。或者可以使用某种哈希结构用来记录每个元素出现的情况,最后遍历。但这样做需要额外的空间,也不满足要求。

那么,我们只能另外想办法了。问题中的条件我们没有充分利用,“其它的元素都出现两次”,为什么是两次,不是三次?这里有何玄机?我们能不能将出现两次的元素这些噪声数据去掉,只剩下我们要求的数据呢?答案是利用异或运算,两个相同的数异或后等于 0,任何数与 0 异或等于本身。

于是,我们可以把 a0,a1,...an 全部异或起来,结果 g = x ^ y,这里的 x 和 y 就是所求的两个只出现一次的元素。现在的问题是,我们手上只有一个数 g ,那么,如何把它利用起来呢?

我们设想一种简单的问题,如果这个数组中只有一个出现一次的数,那么,g 就是所求的数。

我们随时要有分治以及合并的思想!

观察一下,将这个简单的问题合并不就是所求的问题吗?现在有两个数组,a0,a1,a2...an 和 b0,b1,b2...bn ,两个数组里面各只有一个出现一次的数字,其它的数都出现了两次,且这两个数组没有公共的数字。我们合并这两个数组,不就是所求的问题了吗?

反过来,我们能不能把所求的问题转换为两个简单的问题再解决呢?设两个出现一次的元素分别为 x 和 y。

转换有两个关键点:

1.将 x 和 y 划分到不同的数组中。

2.相同的元素要划分到同一个数组中。

这实在是一个比较难的问题,因为现在可以利用的条件只有 g 这个值。划分需要借用一个技巧,这里直接给出吧。

我们想像 g 上面为 1 的那些位(x !=  y,所以 g 不 0,因此 g 不可能全部位都为 0,必存在某一位为 1),1 = 0 ^ 1 = 1 ^ 0。我们可以利用某一个位上的值(0 或者 1)来划分数组,刚好它把 x  和 y 区分开。我们任意选择 g 的二进制位上为 1 的那一位,设为第 p 位。作如下划分:

(原问题的)数组 a0,a1,a2...an 中,每个元素的值的第 p 位为 1 的划分到 A1 数组,否则划分到 A2 数组。再分别求这两个数组的惟一元素。当然,并不能真正构建两个数组,因为问题有空间复杂度的限制。下面给出相关核心代码:



问题推广

如果问题改为:给定一个数组 a0,a1,a2,...an ,其中只有三个元素出现一次,其它的元素都出现两次,找出这三个元素是多少。

我们还用原来的办法来做,划分为两个数组的时候会遇到麻烦。因为求所有元素异或的结果 g 可能等于0。(三个不相同的数字异或可能为0,如a = 1,b = 2,c = 3时,a ^ b ^ c = 0)。这是一个不好解决的点。

退一步讲,设 g != 0,取 g 的二进制中最后一次出现 1 的位为 b ,因为 1 = 0 ^ 1 ^ 0 = 1 ^ 1 ^ 1;也就是说,存在两种情况:设三个只出现一次的元素是 x , y ,z,

1. x,y,z第 b 位全为 1

2.某两个第 b 位为0,一个为1

如果是第二种情况还比较好解决,我们先处理那个第 b 位为 1 的元素所在的数组,可以得到这个元素,那么,我们在原数组中去掉这个元素,于是问题又转换为了上面一个问题。

如果是第一种情况,似乎是不好解决的。

综上,这个问题与上面的问题不太一样,不能以 g 的二进制位为 1 来划分数组。得另外想办法。但我们必须牢牢记住一点,我们可以用于划分的条件只有 g。所以,我们还是得围绕 g 来想办法。我们用 g 与每一个元素再异或,能不能有所发现呢?设三个所求的元素是 x,y,z。

我们现在的任务是找到可区分 g ^ x 和 g ^y 和g ^ z 这三个数字的条件。如果要划分,肯定还是以某位是否为 1 来划分的,这意味着,先要证明这三个数都是非 0的。

由于g = x ^ y ^ z ^ (其它元素两两相异或) = x ^ y ^ z,那么:

g ^ x = y ^ z

g ^ y = x ^ z

g ^ z = x ^ y

而 x,y,z三个数字两两不等,所以 g ^ x , g ^ y , g ^ z 都不为 0;以这三个数字分别为 gx,gy,gz,延续之前的思路,能不能以 gx,gy,gz 某一位为 1 的位 b 作为区分条件呢?这就意味着,这三个数字分别的 b,即b1, b2 ,b3 不能全部为 1,否则不能区分。

我们用反证法来证明。如果这三个数字的第 b 位都为1,则 y ^ z ,x ^ z ,x ^ y 的第 b 位都为1。可以假设 y, z 第 b 位分别为 0,1或者1,0,而 x ,z 第 b 位分别为 0,1或者 1 ,0;z 为假设的共同所在,所以存在以下取值:

y,z,x : 0,1,0 或 1,0,1。这两种取值都导致 y ^ x = 0,这与之前的假设矛盾,所以 y ^ z ,x ^ z ,x ^ y 的第 b 位不都为 1。

其实也可以这样证明,由于 gx ^ gy ^ gz = (y ^ z) ^ (x ^ z) ^ (x ^ y) = g ^ g ^ g ^ x ^ y ^ z = g ^ g ^ g ^ g = 0,所以,不可能出现 gx , gy ,gz 这三个数字的某一位同时为 1,否则 gx ^ gy ^ gz 就不为 0 了,与上面矛盾,而且,这三个数字的同一位上的值只有可能是这样的:两个 1,一个0;或者三个 0;

因此,可以用这样的步骤去划分:

1.计算 gx, gy, gz

2.取 gx 的最后一位为 1 的那个位,记为第 b 位,则 gy , gz 中必有一个的 b 位为1,一个为0;

3.将 b 位上为 1 的划分到一个数组,为 0 的划分到一个数组。

4.找出 b 位为 0 的那个只出现一次的元素,将它从原数组中剔除(放到原数组的末尾)。

5.直接使用上一个问题的解法,找出剩下的两个只出现一次的元素。


至此,问题得解。


0 0
原创粉丝点击