一、引言
这是一道非常非常有趣,题目要求非常非常简单,做出来也非常非常容易,但是最优解却非常非常令人无奈的一道题:
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?
大概意思非常简单,我们获得一个整型数组,其中的元素呢,只有一个是单着的,其他都是成双的,让我们找到这个单个的值并返回。
题目要求算法复杂度在线性程度,并且尽量减少额外的存储空间的利用。
这是一道,怎么说呢,看上去非常非常简单,但是想要达到题设要求又比较困难的题目。不如这样,然我们先来探索,能够用什么样的方法来解决这个问题。
二、鄙人不才,想出了两个方法
首先,要找到这个单着的元素,我们需要干什么呢?
比较。
对的,比较,那么找到计数为 1 的不就可以了吗?
于是乎,map 呼之而来:
// use std::map , runtime = 23 ms
class Solution1 {
public:
int singleNumber(vector<int> &nums) {
map<int, int> map;
for (auto i : nums) ++map[i];
for (auto j : map) if (j.second == 1) return j.first;
}
};
注释里面的 runtime 是我在 LeetCode 上实跑出来的计时值。
类似的,想到了 map,自然也能想到 set,思路是一样的:
// use std::set , runtime = 25 ms
class Solution2 {
public:
int singleNumber(vector<int> &nums) {
set<int> set;
for (auto i : nums)
if (set.find(i) == set.end()) set.insert(i);
else set.erase(i);
return *set.begin();
}
};
哈哈,使用 set 反而比使用 map 还要慢,估计是因为 find 方法的缘故吧。
那么问题来了,这两个方法都不是 O(n) 复杂度的算法,并且使用了额外的 nums.size()
个存储空间,有什么好的方法呢?
我百思不得其解.
我百思不得其解..
我百思不得其解…
于是乎,点开了 Discuss
.
于是乎,点开了 Discuss
..
于是乎,点开了 Discuss
…
三、XOR is always the trick…
看到这个评论,内心是非常戏剧性的:
哈哈哈,又被异或给坑了的感觉油然而生…
让我们看看这段代码:
// use xor , runtime = 13 ms
class Solution3 {
public:
int singleNumber(vector<int> &nums) {
int single = 0;
for (auto i : nums) single ^= i;
return single;
}
};
看不懂吧?
没事,有个外国兄弟解释的非常清楚:
For anyone who didn’t understood why this works here is an explanation. This XOR operation works because it’s like XORing all the numbers by itself. So if the array is {2,1,4,5,2,4,1} then it will be like we are performing this operation
((2^2)^(1^1)^(4^4)^(5)) => (0^0^0^5) => 5.
Hence picking the odd one out ( 5 in this case).
看明白了吗?看不懂我来解释解释。
也就是说,我们遍历的时候,循环异或赋值本身,因为
相同为0,不同为1
异或原则,总有一个单着的无法异或掉,前面所有的相同的异或掉只剩下了一堆 0, 而 0 又异或这个单着的值,而又由于在计算机中,非 0 值均为真值,这里自然就返回了不同为真的值 5。
真的非常非常的奇妙!
异或的妙用我已经见了不少了,博客里零零碎碎也记了很多。
最后以那个外国兄弟的感慨结尾:
exactly :) XOR is always the trick