这道题是面试中的经典题,考察的是我们对位运算的理解和应用。目标是在给定数组中找出那个只出现一次的元素,而其他元素都恰好出现了两次。同时,我们还需要满足时间复杂度 O(n) 和空间复杂度 O(1) 的限制。
为什么是 O(n) 时间复杂度和 O(1) 空间复杂度?
O(n) 时间复杂度 意味着我们需要遍历数组一次(或者有限次)来找到目标元素。我们不能进行嵌套循环(比如暴力法),也不能使用需要对数组进行排序(比如快速排序,平均 O(n log n))的方法。
O(1) 空间复杂度 意味着我们不能使用额外的数据结构来存储中间结果,例如哈希表(HashSet/HashMap)或另一个数组,因为这些数据结构的占用空间会随着输入数组的大小而增长,不符合 O(1) 的要求。
暴力法(不符合要求)
最直观的办法是遍历数组,对于每一个元素,再去遍历一遍数组看它出现了多少次。
```python
def find_single_number_brute_force(nums):
for num in nums:
count = 0
for other_num in nums:
if num == other_num:
count += 1
if count == 1:
return num
return None 如果没有唯一元素,返回None
```
时间复杂度: 对于数组中的每个元素,我们都进行了另一次遍历,所以是 O(n^2)。
空间复杂度: 我们只用了几个变量来计数,所以是 O(1)。
这个方法虽然空间复杂度达标,但时间复杂度太高,不符合要求。
哈希表法(不符合要求)
另一种常见的方法是使用哈希表(例如 Python 中的 `set` 或 `dict`)来记录每个元素出现的次数。
```python
def find_single_number_hash_table(nums):
counts = {}
for num in nums:
counts[num] = counts.get(num, 0) + 1
for num, count in counts.items():
if count == 1:
return num
return None
```
时间复杂度: 第一次遍历计数 O(n),第二次遍历查找 O(n),总共是 O(n)。
空间复杂度: 在最坏的情况下(所有元素都不同,虽然题目说只有唯一一个不重复的,但如果题目变化一下),哈希表会存储数组中所有不重复的元素,空间复杂度是 O(n)。即使题目保证只有唯一一个不重复元素,但哈希表也需要存储所有出现两次的元素及其计数,空间复杂度也取决于不重复元素的数量,所以不满足 O(1)。
这种方法时间复杂度达标,但空间复杂度不达标。
位运算的秘密:XOR (异或)
题目要求 O(n) 时间复杂度和 O(1) 空间复杂度,这往往暗示着需要用到位运算。其中,异或 (XOR) 运算是解决这类问题的绝佳工具。
让我们回顾一下异或运算的几个重要性质:
1. 任何数与 0 异或,结果还是它本身: `a ^ 0 = a`
2. 任何数与它本身异或,结果是 0: `a ^ a = 0`
3. 异或运算满足交换律和结合律: `a ^ b ^ c = a ^ c ^ b` (交换律),`(a ^ b) ^ c = a ^ (b ^ c)` (结合律)
如何利用异或找出唯一不重复的元素?
假设我们的数组是 `[2, 1, 2, 3, 1]`。我们需要找出那个唯一的 `3`。
让我们把数组中的所有元素进行异或操作:
`result = 2 ^ 1 ^ 2 ^ 3 ^ 1`
根据异或的交换律和结合律,我们可以重新排列一下顺序:
`result = (2 ^ 2) ^ (1 ^ 1) ^ 3`
根据 `a ^ a = 0` 的性质,我们知道:
`2 ^ 2 = 0`
`1 ^ 1 = 0`
所以,表达式变成:
`result = 0 ^ 0 ^ 3`
最后,根据 `a ^ 0 = a` 的性质:
`result = 0 ^ 3 = 3`
结果就是那个唯一不重复的元素!
为什么这个方法有效?
当一个元素出现两次时,它会参与两次异或运算。例如,如果 `x` 出现两次,那么在整个异或过程中就会出现 `x ^ x`,根据性质 2,`x ^ x` 的结果是 `0`。这意味着成对出现的元素在整体异或的结果中会相互抵消,不会影响最终结果。而那个唯一不重复的元素,只参与一次异或运算,最终就会保留下来。
具体的实现思路
1. 初始化一个变量 `result`,将其设置为 `0`。
2. 遍历数组中的每一个元素 `num`。
3. 将 `result` 与当前的 `num` 进行异或运算,并将结果赋回给 `result`:`result = result ^ num`。
4. 当遍历完成后,`result` 中存储的就是那个唯一不重复的元素。
代码实现
```python
def find_single_number_xor(nums):
"""
在数组中寻找唯一不重复的元素,其他元素都恰好出现两次。
要求时间复杂度 O(n),空间复杂度 O(1)。
Args:
nums: 一个整数数组。
Returns:
数组中唯一不重复的元素。
如果数组为空或者没有满足条件的元素( যদিও题目保证有),则行为未定义。
"""
if not nums:
根据具体要求,空数组可能需要抛出异常或返回特定值
在这个场景下,假设输入数组总是包含满足条件的元素
return None 或者抛出 ValueError("输入数组为空")
single_number = 0 初始化为 0,因为 x ^ 0 = x
for num in nums:
single_number = single_number ^ num
return single_number
```
例子
```python
示例 1
arr1 = [2, 2, 1]
print(f"数组: {arr1}, 唯一不重复的元素是: {find_single_number_xor(arr1)}") 输出: 1
示例 2
arr2 = [4, 1, 2, 1, 2]
print(f"数组: {arr2}, 唯一不重复的元素是: {find_single_number_xor(arr2)}") 输出: 4
示例 3
arr3 = [1]
print(f"数组: {arr3}, 唯一不重复的元素是: {find_single_number_xor(arr3)}") 输出: 1
示例 4 (包含负数,异或也同样适用)
arr4 = [1, 2, 1, 3, 2]
print(f"数组: {arr4}, 唯一不重复的元素是: {find_single_number_xor(arr4)}") 输出: 3
```
复杂度分析
时间复杂度: 我们只需要遍历数组一次,对每个元素执行一次异或操作。所以时间复杂度是 O(n),其中 n 是数组的长度。
空间复杂度: 我们只使用了一个额外的变量 `single_number` 来存储中间结果,这个变量的占用空间是固定的,与输入数组的大小无关。所以空间复杂度是 O(1)。
总结
通过利用异或运算的“自抵消”特性,我们可以巧妙地在一次遍历中找到那个唯一不重复的元素,同时满足了 O(n) 的时间复杂度和 O(1) 的空间复杂度要求。这是一种非常高效且优雅的解决方案,也是面试官非常青睐的答案之一。掌握这种位运算的技巧,能让你在解决许多编码问题时事半功倍。