有限状态机

  • 前言
  • 一、例题
  • 1、HashMap
  • 2、位运算
  • 3、有限状态机
  • a、如何更新?
  • b、源码
  • 总结
  • 参考文献
  • 附录
  • 补充知识


前言

有限状态机,顾名思义有限个状态在事件的触发下做出相应状态的转换。

一、例题

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

1、HashMap

可以用Map记录一个元素出现的次数,然后遍历Map得到记录次数为1的数字。

public int singleNumber3(int[] nums) {
        //用map记录该数字是否记录过
        Map<Integer, Integer> record = new HashMap<>();
        for (int num : nums) {
            record.put(num, record.getOrDefault(num, 0) + 1);
        }
        //寻找map中记录中只记录一次的数字
        for (int num : nums) {
            if (record.get(num) == 1)
                return num;
        }
        return 0;
    }

2、位运算

Integer,Java中4字节,4 x 8 = 32位,统计每一位为1出现的次数,再对3进行取余,就可以得到单次出现数字该位的值。

public int singleNumber4(int[] nums) {
        //1.按位相加
        int[] bit = new int[32];
        for (int num : nums) {
            for (int i = 0; i < 32; i++) {
                bit[i] += (num & (1 << i)) >> i;
            }
        }
        //2.按位对3取余得到结果的每一位
        int res = 0;
        for (int i = 0; i < 32; i++) {
            res |= (bit[i] % 3) << i;
        }
        return res;
    }

3、有限状态机

第二种方法种的对3取余,暗含着每一位上有3种状态,即0,1,2,即状态机0->1->2->0。
1)触发事件为0,状态不发生改变。
2)触发事件为1,状态0 -> 1 -> 2 -> 0这样改变。

java状态机测例 java 状态机_Java


注:图片来自于leetcode大神-------K神。


对于二进制,用2位来表示这三个状态,即00 -> 01 -> 10。然后接下来的目的就是更新这两位。(在此记为two one 两位)

a、如何更新?

  1. 更新one位:
if two == 0:
  if n == 0:
    one = one
  if n == 1:
    one = ~one
if two == 1:
    one = 0

如何简化?内层配合two的值,变换的过程就像一个异或过程。

if two == 0:
    one = one ^ n
if two == 1:
    one = 0

再次简化,与外层two配合,整个内层与外层就像是逻辑与过程。

one = one ^ n & ~two

java状态机测例 java 状态机_java状态机测例_02

  1. 更新two位
    1)本来状态转移之后应该是01->10->00,但是先更新one位,得到01->00->10.
    2)把01->00->10也看成一个状态机,然后根据这个新状态机更新two位,得到01->10->00.(即我们要的结果)
    3)那是不是根据新状态机要再搞一个表达式来更新two?我们发现新状态机01->00->10跟旧状态机10->00->01的两位刚好相反,所以交换位置,用旧表达式来完成更新two.
  2. 如何实施?
    1)上面分析1位的状态如何,Integer有32位,可以同时运算。
    2)最后的状态只有00、01.只需取低位0或1来表示当前位的值。(即取one位)

b、源码

public int singleNumber5(int[] nums) {
        int ones = 0, twos = 0;
        for (int num : nums) {
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }
        return ones;
    }

总结

1)HashMap
2)位运算
3)有限状态机(二进制状态转换的简化过程)

参考文献

[1] Leetcode原题 [2] K神 [3] 数据结构与算法 [4] Java取反 [5] 运算符详解及优先级

附录

补充知识

  1. 异或、源码、反码、补码
  2. 运算符优先级
  3. Java中取反操作
    1)二进制取反:~1 = 0;~0 = 1;
    2)Java中数字取反:
    1.在计算机中,所有数据的表示方式都是以补码的方式存在;
    2.正数:符号位为0,原码,反码,补码相同;
    3.负数:符号位为1,补码 = 反码 + 1。

A. 正数取反解析:
step 1:先求得该数的原码;
step 2:原码取反得到答案原码;
step 3:答案原码转为补码,最后化为数值。

B. 负数取反解析:
step 1:先求得该数的原码;
step 2:再求得该数的补码;
step 3:补码取反得到答案补码,最后化为数值。

C. 举例说明:
(1)、~5 = -6过程解析
step 1:表示5的原码
5的原码为:00000000 00000000 00000000 00000101。
step 2:对5的原码取反
得到答案的原码:11111111 11111111 11111111 11111010
step 3:将答案的原码转成补码
通过原码得到反码:10000000 00000000 00000000 00000101
补码 = 反码 + 1:
答案的补码为:10000000 00000000 00000000 00000110
得到值:-6
前文中的答案4就是依此而来,10 + ~5 即为 10 + (-6)= 4。

D. 小结
取反操作符(~)结论总结:
当n为正数时,~(n) = -(n+1)
当n为负数时,~(-n) = n - 1,忽略负号