一刷207-有限状态自动机-剑指-004只出现一次的数字II(m)(剑指 Offer 56 - II. 数组中数字出现的次数 II)

题目:
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
-------------------
示例 :
输入:nums = [3,4,3,3]
输出:4

输入:nums = [9,1,7,9,7,9,7]
输出:1
 
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31
-------------------
思考:
个人解法:
class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer, Boolean> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, !map.containsKey(num));
        }
        for (int num : nums) {
            if (map.get(num)) return num;
        }
        return -1;
    }
}
--------------------
如下图所示,考虑数字的二进制形式,对于出现三次的数字,各 二进制位 出现的次数都是 3 的倍数。
因此,统计所有数字的各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字。

在这里插入图片描述

有限状态自动机:
各二进制位的 位运算规则相同 ,因此只需考虑一位即可。
如下图所示,对于所有数字中的某二进制位 1 的个数,存在 3 种状态,即对 3 余数为 0, 1, 2 。

若输入二进制位 1 ,则状态按照以下顺序转换;
若输入二进制位 0 ,则状态不变。
----------------

在这里插入图片描述

如下图所示,由于二进制只能表示 0, 1 ,因此需要使用两个二进制位来表示 3 个状态。
设此两位分别为 two , one ,则状态转换变为:

在这里插入图片描述

接下来,需要通过 状态转换表 导出 状态转换的计算公式 。
首先回忆一下位运算特点,对于任意二进制位 x ,有:
异或运算:x ^ 0 = x​ , x ^ 1 = ~x
与运算:x & 0 = 0 , x & 1 = x
---------
计算 one方法:
设当前状态为 two one ,此时输入二进制位 n 。
如下图所示,通过对状态表的情况拆分,可推出 one 的计算方法为:
if two == 0:
  if n == 0:
    one = one
  if n == 1:
    one = ~one
if two == 1:
    one = 0
引入 异或运算 ,可将以上拆分简化为:
if two == 0:
    one = one ^ n
if two == 1:
    one = 0
引入 与运算 ,可继续简化为:
one = one ^ n & ~two

在这里插入图片描述

计算 two 方法:
由于是先计算 one ,因此应在新 one 的基础上计算 two 。
如下图所示,修改为新 one 后,得到了新的状态图。观察发现,可以使用同样的方法计算 two ,即:
two = two ^ n & ~one
----

在这里插入图片描述

返回值:
以上是对数字的二进制中 “一位” 的分析,而 int 类型的其他 31 位具有相同的运算规则,
因此可将以上公式直接套用在 32 位数上。

遍历完所有数字后,各二进制位都处于状态 00 和状态 01 (取决于 “只出现一次的数字” 的各二进制位是 1 还是 0 ),而此两状态是由 one 来记录的(此两状态下 twos 恒为 0 ),因此返回 ones 即可。
-------------
复杂度分析:
时间复杂度 O(N): 
其中 N 位数组 nums 的长度;遍历数组占用 O(N) ,每轮中的常数个位运算操作占用 O(32×3×2)=O(1) 
空间复杂度 O(1) : 
变量 ones, twos 使用常数大小的额外空间。
---------
class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0;
        int twos = 0;
        for (int num : nums) {
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }
        return ones;
    }
}

LC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值