题目链接:leetcode137
题目大意
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
示例 1:
输入: [2,2,3,2]
输出: 3
示例 2:
输入: [0,1,0,1,0,1,99]
输出: 99
解题思路
朴素算法
我们可以利用一个 HashSet 统计每个数字出现的次数,于是就可以在时间复杂度与空间复杂度均为 O ( n ) O(n) O(n)下找到答案
状态自动机
根据这串数字的特点我们可以发现,对于序列中任意一种数字,不是出现1次就是出现3次。那么,此时考虑其二进制下每一位的特征,我们把所有数字按位合并(这里的合并指计算出每位「1」的个数),容易得出对于任意一位「1」的个数只有0、1、3、4四种状态。显然,每位的状态对应到答案的映射情况为:如果状态为0、3则对应0;否则对应1。
根据上述的算法,我们可以得到答案,但统计的逻辑在线性时间下不太容易做到,此时我们引入状态自动机的方法。因为对于没一位的输入非0即1,但对于状态的设计我们需要修改一下,以便于自动机状态的切换。设计如下:
我们发现这种状态图中「2」的状态不可能到达,或者说最终不可能停留在状态2,于是可以优化一下状态图:
但我们发现,状态3和状态0实际上是等价的,因为状态0输入1后也能转移到结果为1的状态,除此之外,整个状态转移的步长最多为4,所以原本4 – > 3的状态转移实际上最多只会走一次,那么,我们就可以讲「0」和「3」合并,但为了状态表述直观,我们重新处理一下状态,我们用两位二进制数表示三个状态:
根据状态转移我们可以列出真值表,并且根据真值表可以写出成真与或式,然后化简即可:
化简后对于x由于需要使用到上一个y,但此时y已经更新,所以可能需要用到中间变量存储(对于如果先计算y同样存在这个问题),显然有些麻烦,于是我们可以换个变量,把更新后的y带入到x表达式中,作为它的因变量:
此时,得到两个类似的式子。同理,化简逻辑的方式还有很多,例如卡诺图、根据真值表一步步枚举化简等方式,就不一一列举。注意,最终的答案根据状态自动机我们可以发现,和y相关,所以输出y的所表示的每个二进制位的数即可。(由于计算机内部采用二进制运算,所以可以所有的位同时进行状态转移)
代码实现
class Solution {
public:
int singleNumber(vector<int>& nums) {
int x = 0, y = 0;
for (int i = 0; i < nums.size(); i++) {
y = y ^ nums[i] & ~x;
x = x ^ nums[i] & ~y;
}
return y;
}
};