今天在 codewars 上做题的时候发现了这道题,其中最高票的答案简直写的感觉这道题就是为了这个答案量身定做的一样。先来看看题目:
给定一个整数数组,找出数组中出现了奇数次的那个数。题目保证只会有一个数出现奇数次。
然后这是我的答案:
function findOdd(A) {
return A.sort((a,b) => a - b).reduce((result, cur) => {
if(result[result.length -1] === cur) {
result.pop()
} else {
result.push(cur)
}
return result
}, [])[0]
}
我的思路是,如果一个数是出现偶数次的话,那么它两两抵消,就不会存在在我最终 reduce 输出的结果数组中,而如果是奇数次的话,两两抵消后,那么 reduce 出来的最终数组中,就刚好会留下那一个多出来的奇数。
我的结果跑出来没有问题。
那么下面来看一下最高票的答案是怎么只用一行代码做到的:
只用到异或运算,就完成了这道题。
异或运算
// 1. Truth table
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
// 2. Properties:
A ^ A = 0 ( ex: 10 ^ 10 = 0 )
A ^ 0 = A
// 3. Associativity:
a ^ b ^ c = a ^ c ^ b
or even
a ^ b ^ c ^ d = a ^ c ^ d ^ b
// 对于这道题而言我们了解以上属性就足够了
那下面我们一起来看一下这道题:
对于数组[10, 3, 20, 10, 3, 20, 10]
,数字 10
是出现奇数次的那一个数,即是我们的最终结果。
那么我们对这个数组进行异或运算的话,迭代如下:
- 10 ^ 3 = A (假设结果为 A,因为我们不关心这个数值,它不重要)
- A ^ 20 = B (同理 B 也是一个中间结果,不重要)
- 10 ^ 3 ^ 20 ^ 10 这一步可以交换顺序,等价于
10 ^ 10 ^ 3 ^ 20
从上面的 properties 可以知道 A ^ A = 0
所以 10 ^ 10 = 0 ... 所以变为 0 ^ 3 ^ 20, 又因为 A ^ 0 = A
所以 上面的式子最终为 3 ^ 20
- 3 ^ 20 ^ 3 = 3 ^ 3 ^ 20 = 0 ^ 20 = 20
- 20 ^ 20 = 0
- 0 ^ 10 = 10
最终留下的,就是出现奇数次的这个数。
局限
但这个方法局限性也很大,比如说如果数组中没有奇数,则结果为 0,这与数组中有一个奇数 0 的结果相同。
而且如果是给定的数组中有多个奇数的情况,那么结果也是不可预测的。
所以这个解决方案的确非常聪明,很适合题目,但并不适合在实际开发中使用...