题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。
请写程序找出这两个只出现一次的数字。
你可以假设这两个数字一定存在。
样例
输入:[1,2,3,3,4,4]
输出:[1,2]
算法
(位运算) $O(n)$
首先我们知道求数组中只出现一次的一个数字的求法,因为除了这个数其他数都出现了两次,所以将所有数异或起来的结果就是答案。
这道题目要求的是只出现一次的两个数字,我们需要想办法把它拆解成第一类问题
假设两个数字为 $x、y$
- 先将所有数异或起来的结果记为
sum = x ^ y
,因为其他数字都出现两次,它们异或的结果是 $0$ - 由于
x != y
则 $x$ 和 $y$ 不全为 $0$,所以 $sum$ 必不为 $0$,那么 $sum$ 的二进制表示中肯定有 $1$,且对于为 $1$ 位,$x$ 和 $y$ 必然其中一个为 $1$,另一个为 $0$,否则异或的结果当前位不可能为 $1$,按照 $sum$ 的二进制表示中最后一个 $1$(假设是第 k 位) 所有数划分为两个集合,一个集合中第 $k$ 位全为 $1$,另一个集合中第 $k$ 位全为 $0$,且 $x$ 和 $y$ 不在同一个集合 - 此时问题就转换成了求两个「数组中只出现一次的*一个数字」,对两个集合分别异或起来,得到的就是 $x$ 和 $y$
优化:只求一个集合的异或结果可以得到一个数 $first (= x / y)$,而已知
sum = x ^ y
,则 $first$ ^ $sum$ 就是另一个数
时间复杂度
考虑最长时间就是遍历数组所花费的时间,时间复杂度为 $O(n)$
C++ 代码
class Solution {
public:
vector<int> findNumsAppearOnce(vector<int>& nums) {
int sum = 0;
for (auto x : nums) sum ^= x;
int k = 0;
while (!(sum >> k & 1)) k ++ ;
int first = 0;
for (auto x : nums)
if (x >> k & 1) first ^= x;
return vector<int>({first, sum ^ first});
}
};