很久以前,参加X公司面试,遇到这样一道题目:
在数组中,只出现一次的数叫“孤单数”, 某数组a中有两个“孤单数”,而其余数字出现次数都为偶数次,求这两个“孤单数”。要求:时间复杂度为O(n), 空间复杂度为O(1).
这个问题的最直观解法是:直接对数组元素进行计数。但是,这样空间复杂度必然无法满足要求,怎么办呢?
可以考虑异或。异或,是一种重要的操作,在之前的文章中,介绍过如下电路图,其功能是计算两个1位二进制值的异或:
D1和D2异或的结果D3如下:
D1 | 操作 | D2 | D3 |
0 | 异或 | 0 | 0 |
0 | 异或 | 1 | 1 |
1 | 异或 | 0 | 1 |
1 | 异或 | 1 | 0 |
可以看到,异或其实就是不考虑进位的二进制加法。实际上,计算机中的加法就是用异或来实现的。
那么,对于两个整数而言,它们的异或结果,就是按位异或的值,我们来看下3和5的异或:
可以看出,a和b异或的结果如下(异或符号是^):
1. 当a不等于b, a^b不为0;
2. 当a=b, a^b为0;
3. 当b=0, a^b为a.
假如数组a中只有一个“孤单数”, 而其它数字都出现偶数次时,把所有数字异或,就会得到该“孤单数”,程序如下:
package main
import "fmt"
func findSole(a []int) int {
n := 0
for _, v := range a {
n = n ^ v
}
return n
}
func main() {
a := []int{2, 1, 2, 3, 5, 1, 3} // 只有1个“孤单数”
fmt.Println(findSole(a)) // 5是“孤单数”
}
回头再看实际题目,数组a中有两个“孤单数”,那怎么办呢?
假设数组a为{2, 1, 2, 3, 5, 1},如果把所有数字异或,得到的结果是3和5的异或值(也即为6),无法分解出3和5.
我们再观察下3和5的异或:
可以看到,从二进制层面来看,3和5的倒数第二位分别是1和0,所以,我们可以把数组a分类两类:
第一类: 二进制数字倒数第二位为1的数, 比如3, k1, k2, k1, k2.
第二类: 二进制数字倒数第二位为0的数,比如5, k3, k4, k4, k3.
现在,我们对这两类数字分别进行异或,很容易就求出了3和5这两个“孤单数”。
异或,是一种很巧妙的思维,这取决于异或独特的性质。在实际开发中,我们会偶尔用到异或,而且,这类按位运算是非常快的。在一些笔试面试中,异或也是常考的内容之一。
周末愉快。