题目描述
Given an array of integers, every element appears twice except for one. Find that single one.
Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
思考
给定一个整数数列,其中除了一个元素之外,所有的元素都出现了两次,找到这个只出现一次的元素。看到本题的第一想法就是将所有元素的出现次数记录下来不就行了么?但是该解法的时间复杂度是多少呢?记数组的长度为N,那么对元素组遍历一次所需的时间是O(N),同时,我们将创建一个新的数组对元素出现的次数进行记录,最终返回只出现一次的元素,这一部分的时间复杂度是O(N^2),因为需要对新数组进行N次查找操作。显然这样做的话时间复杂度就不符合题目中要求的线性复杂度了,那我们应该怎么办呢?
如果这个整数数列中的最大整数并不是非常大的情况下,我们是可以用空间来换取时间的。假设给出的整数数列中的最大整数为M,那么我们可以创建一个新数组,新数组的大小为M+1,初值均为0,接下来遍历原始数列,出现某个整数,则在对应的index处+1,最后遍历新数列,找出值为1的index即可。可是这种方法的实现有一个前提,就是我们需要知道原始数列中的最大整数,但是我们并不知道,当然,我们可以对数列遍历一次,通过比较得出最大元素。
上面这种方法有什么问题呢?最大整数M如果是负数怎么办?要知道负数是不能当下标的。虽然我们有相应的解决方法,比如说我们可以加上maxN,或者是对A中所有的数据取负号,总而言之就是将原始数列中的元素变为正数,并放入B数列中,然后进行上述步骤,可是这太繁琐了,不是么?此外,当M很大的时候,这种方法需要new一块极大的内存,这并不是我们想看见的。
如此看来,我们需要继续寻找新的解法。如何既能保证线性时间复杂度,又可以不引入额外的空间呢?
我们需要对题目做进一步的思考。题中强调除了某一个元素外,其他元素都将出现两次,我们能否利用出现两次将其抵消?就类似于我们可以标记每一个元素为“-”,出现一次即为“-”,两次就因为负负得正变为了“+”。Ok,进一步联想到异或操作的特殊性,两个相同的整数进行异或,得到的结果将为0,那么我们遍历原始数组A,对其中的元素进行异或,那么最后得到的值不正是我们想要的元素么?实际上这种方法的本质就是考查int的各个位上的0/1情况,如果某个位置上1出现两次,那么则清0,这样的话就可以按照题目要求找到那个single-number了。
给出代码如下:
class Solution {
public:
int singleNumber(int A[], int n) {
int num = 0;
for(int i = 0; i < n; i++){
num ^= A[i];
}
return num;
}
};
感觉很多题的方法都很巧妙,但巧妙算法的根源就是题目中数据的特质,比如本题中除了某个元素之外,其他元素均出现两次,所以一定要仔细读题目,仔细分析题目中数据的固有性质。