有限状态机
- 前言
- 一、例题
- 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这样改变。注:图片来自于leetcode大神-------K神。
对于二进制,用2位来表示这三个状态,即00 -> 01 -> 10。然后接下来的目的就是更新这两位。(在此记为two one 两位)
a、如何更新?
- 更新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
- 更新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.- 如何实施?
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] 运算符详解及优先级
附录
补充知识
- 异或、源码、反码、补码
- 运算符优先级
- 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,忽略负号