[LeetCode] 137. Single Number II 深入浅出算法讲解和代码示例

1. 审题

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

Note:

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

原题链接:https://leetcode.com/problems/single-number-ii/description/


这道题的难点在于,复杂度要求O(n),且不能开辟额外空间。

可以用位运算的思路来解题。


2. 解题及代码分析

https://leetcode.com/problems/single-number-ii/discuss/ 这里面提到的解题思路较优,且代码量很少(见下):
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;
}
循环主题只有两行代码,较为精妙,我们下面分析下解题思路。
数列中除了我们目标数,其它都出现了三次。将数列中每个数用二进制表示,那在各位上出现的次数至少是3、或3的倍数,从这个思路出发,我们用ones、twos分别统计各位上出现1次、2次的个数。这里如果听不懂的话,也没关系,我们用实例来讲解。

以A = [3, 8, 9, 8, 3, 3, 8]为例,他们的二进制分别是:
3:0011
8:1000
9:1001
通过以下代码循环遍历数列,结果如下:
        ones = (ones ^ A[i]) & ~twos;
        twos = (twos ^ A[i]) & ~ones;
ones =0, twos = 0;
i=0, A[i]=3:
ones twos
0011 0
----- A[0]=0011,每位上的1只出现了1次,所以ones=0011
i=1, A[i]=8:
A[0]: 0 0 1 1
A[1]:1 0 0 0
统计下来:
ones twos
            1011  0
----- A[0],A[1]汇总下来,每位上的1出现1次的是两者异或的结果:1011,

i=2,A[i]=9:
A[0]: 0 0 1 1
A[1]: 1 0 0 0
A[2]: 1 0 0 1
统计下来:
ones twos
0010 1001
----- A[0],A[1],A[2]汇总下来,每位上的1出现1次的是0010,出现2次的是1001。

依次类推。

上面介绍了ones、twos的含义,以及遍历结果。
下面讲解下代码为什么这么写。

以下下为例:
ones = (ones ^ A[i]) & ~twos;

1. (ones ^ A[i])两个数异或是把两个数各自位上的10相减的绝对值。

ones表示历史迭代中各位上出现1的个数,当前A[i]与它异或:

> 每位上两者都是1的,表示历史统计结果ones出现1次、A[i]中又出现1次,则是2次。

> 每位上两者分别为0、1的,纳入ones统计结果。

2.  & ~twos  ---- 是为了出现3次后清0

&~twos表示与twos的非相与,什么意思呢?

(ones ^ A[i])表示历史统计到A[i]为止出现1的个数,假设 = 1100,此时twos=0100,表示在从右向左数第3位上,出现了1次,同时之前也出现了2次(在twos里),即出现了1+2=3次,对于3次的结果需滤除,所以对twos中出现过的,要&~



3. 拓展

该解题思路可拓展到数列中出现大于三次的情况,如4次、5次、6.....

这时可适当灵活定义onestwosthrees....来统计。



你可能感兴趣的:(算法,算法,leetcode)