在线性时间内找出只出现 1 次的数字
本文针对 LeetCode 题目中「找出只出现 1 次的数字」3 道系列题,总结题目规律以及位运算的使用方法。
本文题目难度标识:🟩简单,🟨中等,🟥困难。
题目与方法
首先到 站内文章这篇文章 中补一下前置知识:
- 异或运算的特性
- 取出一个整数的二进制表示中的最低位 lowbit:
x&-x
找出 1 个只出现 1 次的数字(其他数字成俩出现)
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
方法:将所有数字按位异或,最后得到只出现一次的数字。
这里利用了异或的运算性质:
- 交换律、结合律
- 相同数字异或为 0
复杂度:
- 时间复杂度:
- 空间复杂度:
这种方法可以扩展应用于这个问题:数组内某元素 x 出现了奇数次,其他数字出现偶数次,找出这个 x。
找出 1 个只出现 1 次的数字(其他数字成仨出现)
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。 请你找出并返回那个只出现了一次的元素。数组中的元素都在 int(即 32 位整数)范围内。
思路:数组中的元素范围有限,我们可以依次计算答案的每一个二进制位是 0 还是 1。
方法:
- 循环体,复杂度 :遍历
nums中的每个元素e- 循环体,复杂度 :对于每个
e循环统计所有数字第 i 位出现 1 的次数,得到数组sum,sum[i]即表示所有数字第 i 位出现 1 的次数。
- 循环体,复杂度 :对于每个
假设答案为 x:
- 如果
sum[i]是 3 的倍数,那么x的第i位应为 0 - 如果
sum[i]不是 3 的倍数(余数只能为 1),那么x的第i位应为 1
复杂度:
- 时间复杂度:,其中 n 时数组的长度,C 是元素的数据范围。本问题中 。
- 空间复杂度:
这个方法引入了一个复杂度 ,我们可以用有限状态机加速位运算方法(详看后文)进行优化设计。
这种方法可以扩展应用于这个问题:如果数组内元素范围给定,除某 1 个元素 x 仅出现 M 次外,其余每个元素都恰出现 N 次,找出这个 x。(N 为整数且 ,)
如果 M 和 N 的奇偶性比较特殊,可以考虑使用异或性质,就如第一个应用那样。
找出 2 个只出现 1 次的数字(其他数字成俩出现)
给你一个整数数组 nums,其中恰好有两个元素 a、b 只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
方法:
- 将
nums中的所有数字全部异或,得到结果x。x==a^b,且x一定不会是 0 - 取出 x 的二进制表示中最低位的 1:
x&-x。假设其为第l位。这说明,a和b中,某一个数的第l位为 1,另一个数的第l位为 0。 - 把
nums中的所有元素分成两类,其中一类包含所有二进制表示的第l位为 0 的数,另一类包含所有二进制表示的第l位为 1 的数。我们得到以下结论:- 对于任意一个在数组
nums中出现两次的元素,该元素的两次出现会被包含在同一类中; - 对于任意一个在数组
nums中只出现了一次的元素,即a和b,它们会被包含在不同类中。
- 对于任意一个在数组
- 将每一类的元素全部异或起来,那么其中一类会得到
a,另一类会得到b
复杂度:
- 时间复杂度:,其中 n 是数组
nums的长度。 - 空间复杂度:。
这种方法可以扩展应用于这个问题:给定整数数组,有两个元素分别出现奇数次,其余每个元素都恰出现偶数次,找出这个这两个只出现奇数次的元素。
比如:
- 使用位运算解决:🟩 645. 错误的集合 - 力扣(LeetCode)
有限状态机(FSM)加速位运算
线性时间内找出 1 个只出现 1 次的数字(其他数字成仨出现)
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。 请你找出并返回那个只出现了一次的元素。数组中的元素都在 int(即 32 位整数)范围内。
在上文提到的方法中我们是依次处理每一个二进制位的,那么时间复杂度中就引入了 这一项(对于一个数 e,我们循环统计了 次)。我们可以直接使用位运算而避免这个循环,也就是下面的数字电路设计优化。
定义两个数组 A 和 B 作为统计结果使用,长度与元素 e 位数相等。A 和 B 中的元素只能是 0 和 1,A[i] 和 B[i] 组成一个统计结果,表明目前遍历的所有 e 中第 i 位的个数(模 3)。比如,遍历 nums 结束后,A[i]=1,B[i]=0,组成二进制数 0b10,表明目前统计的元素中第 i 位为 1 的个数为 2(模 3)。

根据此,进行类似有限状态机 FSM 中描述的设计方法。
方案 1
绘制状态图:

绘制真值表(省略了状态编码等过程,直接一步到位):

写出输出方程:
将电路逻辑转换为等价的位运算:
1 | a = (~a & b & x) | (a & ~b & ~x) |
在这题中碰巧的是,B 数组组成的二进制数就是所求(因为最终的统计结果中,余数只能是 0 或 1)。
- 时间复杂度:,其中 n 是数组
nums的长度。 - 空间复杂度:。
方案 2
在上一个方案中计算 b 简单,计算 a 麻烦。因此可以将「同时计算」改为「分别计算」,即先计算出 b,再拿新的 b 值计算 a。
也就是说,我们拿新的 b’ 改造一下真值表,看看能不能把电路优化。
进一步优化:

重新写出 a 的输出方程:
将电路逻辑转换为等价的位运算:
1 | b = ~a & (b^x) // b先计算 |
- 时间复杂度:,其中 n 是数组
nums的长度。 - 空间复杂度:。
本文参考
- 《深入理解计算机系统》第 3 版,「数制与编码」章节
- LeetCode 原题——只出现一次的数字 I、II、III





