剑指 Offer 56 - I. 数组中数字出现的次数

题目描述

一个整型数组 $nums$ 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:

1
2
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]

示例 2:

1
2
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

限制:

  • $2 <= nums.length <= 10000$

算法

(位运算,异或) $O(n)$

首先我们知道求数组中只出现一次的一个数字的求法,因为除了这个数其他数都出现了两次,所以将所有数异或起来的结果就是答案。

这道题目要求的是只出现一次的两个数字,我们需要想办法把它拆解成第一类问题

假设两个数字为 $x、y$

  1. 先将所有数异或起来的结果记为 sum = x ^ y,因为其他数字都出现两次,它们异或的结果是 $0$
  2. 由于 x != y 则 $x$ 和 $y$ 不全为 $0$,所以 $sum$ 必不为 $0$,那么 $sum$ 的二进制表示中肯定有 $1$,且对于为 $1$ 位,$x$ 和 $y$ 必然其中一个为 $1$,另一个为 $0$,否则异或的结果当前位不可能为 $1$,按照 $sum$ 的二进制表示中最后一个 $1$(假设是第 k 位) 所有数划分为两个集合,一个集合中第 $k$ 位全为 $1$,另一个集合中第 $k$ 位全为 $0$,且 $x$ 和 $y$ 不在同一个集合
  3. 此时问题就转换成了求两个「数组中只出现一次的一个数字」,对两个集合分别异或起来,得到的就是 $x$ 和 $y$

    优化:只求一个集合的异或结果可以得到一个数 $first (= x / y)$,而已知 sum = x ^ y,则 $first$ ^ $sum$ 就是另一个数

时间复杂度

考虑最长时间就是遍历数组所花费的时间,时间复杂度为 $O(n)$。

空间复杂度

$O(1)$

C++ 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int sum = 0;
for (auto x : nums) sum ^= x;
int t = sum & -sum; // t 表示 sum 的最后一个表示的二进制数,比如 100100,结果就是 100
int first = 0;
for (auto x : nums)
if (x & t) // 如果是 1,则分到和 first 一组
first ^= x;
return {first, sum ^ first};
}
};
Author: tonngw
Link: https://tonngw.com/2022/07/09/剑指 Offer/剑指 Offer 56 - I. 数组中数字出现的次数/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.