方法1 :
map 存储,判断
方法2:
代码:
func singleNumber(nums []int) int {
a, b := 0, 0
for _, num := range nums {
b = (b ^ num) &^ a
a = (a ^ num) &^ b
}
return b
}
解析
/**
* 在方法三中,我们是依次处理每一个二进制位的,那么时间复杂度中就引入了 O(logC) 这一项。
* 我们在对两个整数进行普通的二元运算时,都是将它们看成整体进行处理的,那么是否能看成二进制表示,同时处理所有的二进制位?
*
* 答案是可以的。
*
* 【以某一个二进制位为例】
*
* 对于某一个二进制位,数组中 所有 元素这个二进制位加起来的结果 % 3 的结果 是 0 1 2,也就是有三种状态
* 此时,如果再来一个数字,它的这个二进制位是1,那么 就会有这样的状态变化:0 -> 1, 1 -> 2, 2 -> 0
* (比如 0 -> 1, 就是 前面数字这个二进制位的和是3的整数倍,当前数字这个二进制是1,加上去的和,对3取余,自然结果变为1)
*
* 所以这相当于,在统计所有元素这个二进制位值和的过程中,当前位置的最终结果 是 在 0 - 1 - 2 这三种结果中变化的,就是一个状态转移
* 借助有限状态自助机原理:
*
* 因为二进制只有0和1,但我们有三种状态,所以需要两个二进制位合起来表示这三种状态
*
* 我们可以使用一个「黑盒」存储当前遍历过的所有整数。
* 「黑盒」的第 i 位为 {0,1,2} 三者之一,表示当前遍历过的所有整数的第 i 位之和除以 3 的余数。
* 考虑在「黑盒」中使用两个比特位来进行存储,即:
*
* 黑盒中存储了两个比特位 a 和 b,且会有三种情况:
*
* a 的第 i 位为 0 且 b 的第 i 位为 0,表示 结果 0;(相当于a是高位,b是低位,不过不重要)
* a 的第 i 位为 0 且 b 的第 i 位为 1,表示 结果 1;
* a 的第 i 位为 1 且 b 的第 i 位为 0,表示 结果 2。
* 为了方便叙述,我们用 (00) 表示 a 的第 i 位为 0 且 b 的第 i 位为 0,其余的情况类似。
*
* 当我们遍历到一个新的整数 x 时,对于 x 的第 i 位 xi,我们可以得出下面的真值表:
* (a, b) x(当前数字这个bit位的值) (新的a,新的b)
* (mod3=0) 00 0 00 (mod3=0)
* (mod3=0) 00 1 01 (mod3=1)
* (mod3=1) 01 0 01 (mod3=1)
* (mod3=1) 01 1 10 (mod3=2)
* (mod3=2) 10 0 10 (mod3=2)
* (mod3=2) 10 1 00 (mod3=0)
*
* 如果单独考虑新的a
* (a, b) x(当前数字这个bit位的值) 新的a
* 00 0 0
* 00 1 0
* 01 0 0
* 01 1 1
* 10 0 1
* 10 1 0
*
* 通过真值表写逻辑表达式 新a = !a & b & x | a & !b & !x ,也就是真值表取值为1的那两行 或 起来
*
* 如果单独考虑新的b
* (a, b) x(当前数字这个bit位的值) 新的b
* 00 0 0
* 00 1 1
* 01 0 1
* 01 1 0
* 10 0 0
* 10 1 0
*
* 通过真值表写逻辑表达式 新b = !a & !b & x | !a & b & !x = !a & (!b & x | b & !x) = !a & (b ^ x)
*
* 所以:当前二进制位的结果 (a,b), a ,b,x都是一个二进制位
* 新a = !a & b & x | a & !b & !x
* 新b = !a & (b ^ x)
* 因为最终结果,也就是当前二进制位只可能是 0 或 1,对应我们的状态表示就是 00 或 01,因此 我们不需要关注a,只需要返回 比特b
*
* 【由1个二进制位扩展到32个二进制位同时运算】
*
* 我们可以同时对 32 个二进制位进行运算,每一个二进制位的结果需要 两个比特位 以及 当前数字对应二进制位的值
* 而int型正好是32位,所以用两个int整数就可以完成上面需要的32个a和32个b
*
public int singleNumber4(int[] nums) {
// 32个a,b同时运算
int a = 0, b = 0;
for (int x : nums) {
// 新a = !a & b & x | a & !b & !x
// 新b = !a & (b ^ x)
int na = (~a & b & x) | (a & ~b & ~x), nb = ~a & (b ^ x);
a = na;
b = nb;
}
// 最后只关心32个b
return b;
}
/**
* 方法四改进
* // 新a = !a & b & x | a & !b & !x
* // 新b = !a & (b ^ x)
* 上面过程中,发现 新 b 的计算比较简单,而 新a 的计算比较复杂,
* 上述相当于从 (a,b)和x同时得到 新a 和 新b,然后 分别写出 两个表达式,
* 既然 新b计算简单,那么我们能够改为「分别计算」,即先计算出 b,再拿新的 b 值计算 a。
*
* 原真值表如下:
* (a, b) x(当前数字这个bit位的值) (新的a,新的b)
* (mod3=0) 00 0 00 (mod3=0)
* (mod3=0) 00 1 01 (mod3=1)
* (mod3=1) 01 0 01 (mod3=1)
* (mod3=1) 01 1 10 (mod3=2)
* (mod3=2) 10 0 10 (mod3=2)
* (mod3=2) 10 1 00 (mod3=0)
*
* 如果单独考虑新的b
* (a, b) x(当前数字这个bit位的值) 新的b
* 00 0 0
* 00 1 1
* 01 0 1
* 01 1 0
* 10 0 0
* 10 1 0
*
* 通过真值表写逻辑表达式 新b = !a & !b & x | !a & b & !x = !a & (!b & x | b & !x) = !a & (b ^ x)
*
* 【利用新b计算新a】
* 如果我们将第一列的b换成新b,得到如下
* (a, [新b]) x(当前数字这个bit位的值) 新的a (从上面真值表抄下来,这里不是让你重新计算)
* 0 0 0 0
* 0 1 1 0
* 0 1 0 0
* 0 0 1 1
* 1 0 0 1
* 1 0 1 0
*
* 我们发现 新a = !a & !新b & x | a & !新b & !x = !新b & (a ^ x)
* 这样的话,就简单多了,所以 先计算新b,再计算新a
*
* 新b = !a & (b ^ x)
* 新a = !新b & (a ^ x)
*/
public int singleNumber5(int[] nums) {
// 32个a,b同时运算
int a = 0, b = 0;
for (int x : nums) {
// 新b = !a & (b ^ x)
// 新a = !新b & (a ^ x)
b = ~a & (b ^ x);
a = ~b & (a ^ x);
}
// 最后只关心32个b
return b;
}