卷三 · 代码与实践3 分钟阅读

leetcode题解137:只出现一次的数字 II

描述

该题来自于力扣第137题

分析

这题本身难度并不高,只不过是一种巧妙的解法难以想到。题目要求空间复杂度为常数级,所以哈希表的解法(不合题意)就不介绍了。

先介绍一个简单的解法,位运算。由于其它数字都出现三次,很容易想到mod 3运算,将所有数字变为32位二进制,对于所有数字上的第i位上的二进制位求和,然后mod 3就是最终结果的二进制表示的第i位,这种解法非常简单且容易理解。

上面的解法需要对32位进行遍历,求出每一位上的二进制结果。其实有一种一次同时求出32位上的二进制结果的方法,就是用有限状态机。我们首先看下某一位上的计算过程,已计算的结果用x表示,新来的数位用y表示,那么计算公式为

x=(x+y)mod  3 x = (x + y) \mod 3

状态变化过程:

x y 新x
0 0 0
0 1 1
1 0 1
1 1 2
2 0 2
2 1 0

由于x的取值为0,1,2,所以为了使用位运算计算将x拆分为两位,分别计算两位的状态,合起来就是最终结果,用(a,b)来表示x的高位和低位。状态变化更新为:

状态变化过程:

(a,b) y 新(a,b)
(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)

其实上面就是真值表(假定都学过数字电路),可以推出计算公式为

a1=aˉby+abˉyˉb1=aˉbˉy+aˉbyˉa=a1b=b1 a_1 = \bar aby + a \bar b \bar y \\ b_1 = \bar a \bar b y + \bar a b \bar y \\ a = a_1 \\ b = b_1

不化简也可以。每次来一个y计算出新的(a,b);会发现对于32位中的每一位同时按照这种方式计算,相当对上述运算开启并行度32,得到的结果就是每一位上的二进制结果。

这样写的代码其实已经是符合题意的了,但是公式可以继续简化,比如

b=aˉbˉy+aˉbyˉ=aˉ(b⊕y) b = \bar a \bar b y + \bar a b \bar y = \bar a (b \oplus y)

还可以继续,比如我们先计算出b_1,然后根据(a, b_1)计算出a_1,一样的先画真值表,然后推出公式:
b1=aˉ(b⊕y)a1=bˉ1(a⊕y)a=a1b=b1 b_1 = \bar a (b \oplus y) \\ a_1 = \bar b_1 (a \oplus y) \\ a = a_1 \\ b = b_1

由于(a1,b1)不是同时求出来的,所以写代码的时候可以简化
b=aˉ(b⊕y)a=bˉ(a⊕y) b = \bar a (b \oplus y) \\ a = \bar b (a \oplus y)

我们再来看看最终结果是什么,由于只有一个没有出现三次,所以最终结果只能是(0,0)=0,(0,1)=1,不可能出现(1,0)=2的情况,所以最终结果直接取b的值即可。

代码

python
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        a, b = 0, 0
        for x in nums:
            b = ~a & (b ^ x)
            a = ~b & (a ^ x)
        return b

继续这个系列