137. 只出现一次的数字 II
题目描述
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
示例 1:
输入:nums = [2,2,3,2] 输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99] 输出:99
提示:
1 <= nums.length <= 3 * 104-231 <= nums[i] <= 231 - 1nums中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
解法
方法一:位运算
思考
其余数出现三次、一个出现一次,异或不再直接可用,因为 \(x\oplus x\oplus x=x\)。进阶仍要常数空间。按位统计 \(1\) 的个数再模 \(3\),出现三次的位被清掉,余数即答案对应位。符号位单独处理以免溢出。
我们可以枚举每个二进制位 \(i\),对于每个二进制位,我们统计所有数字在该二进制位上的和,如果该二进制位上的和能被 \(3\) 整除,那么只出现一次的数字在该二进制位上为 \(0\),否则为 \(1\)。
时间复杂度 \(O(n \times \log M)\),空间复杂度 \(O(1)\)。其中 \(n\) 和 \(M\) 分别是数组的长度和数组中元素的范围。
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
方法二:数字电路
思考
方法一对每个位扫描整数组。用两个整数 \(a,b\) 按位表示「该位出现次数模 \(3\)」,读入下一个数时按真值表更新,一遍扫描完成同样的模 \(3\) 计数。
我们考虑一种更高效的方法,该方法使用数字电路来模拟上述的位运算。
一个整数的每个二进制位是 \(0\) 或 \(1\),只能表示 \(2\) 种状态。但我们需要表示当前遍历过的所有整数的第 \(i\) 位之和模 \(3\) 的结果,因此,我们可以使用 \(a\) 和 \(b\) 两个整数来表示。那么会有以下三种情况:
- 整数 \(a\) 的第 \(i\) 位为 \(0\) 且整数 \(b\) 的第 \(i\) 位为 \(0\),表示模 \(3\) 结果是 \(0\);
- 整数 \(a\) 的第 \(i\) 位为 \(0\) 且整数 \(b\) 的第 \(i\) 位为 \(1\),表示模 \(3\) 结果是 \(1\);
- 整数 \(a\) 的第 \(i\) 位为 \(1\) 且整数 \(b\) 的第 \(i\) 位为 \(0\),表示模 \(3\) 结果是 \(2\)。
我们用整数 \(c\) 表示当前要读入的数,那么有以下真值表:
| \(a_i\) | \(b_i\) | \(c_i\) | 新的 \(a_i\) | 新的 \(b_i\) |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 0 | 1 |
| 0 | 1 | 0 | 0 | 1 |
| 0 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 0 |
基于以上真值表,我们可以写出逻辑表达式:
以及:
最后结果是 \(b\),因为 \(b\) 的二进制位上为 \(1\) 时表示这个数字出现了 \(1\) 次。
时间复杂度 \(O(n)\),空间复杂度 \(O(1)\)。其中 \(n\) 是数组的长度。
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 9 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
方法三:哈希表 + 数学
思考
不限制额外空间时,唯一数等于 \((3\sum_{\mathrm{unique}}-\sum_{\mathrm{all}})/2\)。集合去重后做两次求和即可,思路直接,但空间是 \(O(n)\)。
1 2 3 4 5 | |
1 2 3 4 5 | |
方法四:位运算
思考
方法二的状态机可以收成两个掩码:用 \(\textit{ans}\) 与 \(\textit{acc}\) 分别记下「出现一次」「出现两次」的位,按 \(x\) 更新后互斥,最后 \(\textit{ans}\) 即只出现一次的数。实现更短。
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 6 7 8 9 10 | |