一 有限状态机 (Finite State Machine, FSM)
1、基本概念
有限状态机是一种数学计算模型,用于描述系统在不同状态之间的转换行为。它由有限个状态以及这些状态之间的转换规则组成。
2、核心组成要素
- 状态 (State): 系统在某一时刻的运行情况
- 事件 (Event): 触发状态转换的条件
- 转换 (Transition): 从一个状态到另一个状态的变化过程
- 动作 (Action): 状态转换时执行的操作
- 初始状态: 系统的起始状态
- 终止状态: 系统的结束状态(可选)
3、应用场景
- 🎮 游戏开发: 角色AI行为控制(巡逻→追击→攻击)
- 🔐 协议设计: TCP连接状态管理
- 🤖 硬件设计: 数字电路、嵌入式系统
- 💬 编译器: 词法分析器
- 🚦 控制系统: 交通信号灯、自动售货机
- 🎯 工作流: 订单状态管理(待支付→已支付→已发货→已完成)
4、简单示例
交通信号灯状态机:
状态: 红灯 → 绿灯 → 黄灯 → 红灯
事件: 定时器超时
动作: 切换灯光颜色
代码示例 (Python):
class TrafficLight:
def __init__(self):
self.state = "RED"
def change(self):
if self.state == "RED":
self.state = "GREEN"
elif self.state == "GREEN":
self.state = "YELLOW"
elif self.state == "YELLOW":
self.state = "RED"
print(f"当前状态: {self.state}")
二 怎么可以想到用有限状态机解决问题
1、典型特征信号 🚦
当你的问题具有以下特征时,应该考虑FSM:
1.1 有明确的"阶段"或"模式"
❓ 问题中是否有"先...然后...最后..."的描述?
❓ 是否需要"依次处理"某些步骤?
例如:
- myAtoi: 先跳过空格 → 再处理符号 → 再读数字
- 括号匹配: 遇到'(' → 遇到')' → 判断是否匹配
- URL解析: 协议 → 域名 → 路径 → 参数
1.2 处理逻辑随"当前情况"变化
❓ 同样的输入,在不同情况下处理方式不同?
❓ 是否有"如果当前在...状态,遇到...就..."的逻辑?
例如:
- 在"已读符号"状态遇到数字 → 开始累加
- 在"初始"状态遇到数字 → 直接累加
- 在"数字"状态遇到字母 → 结束
1.3 顺序处理字符/事件流
❓ 是否需要逐个字符/事件处理?
❓ 前面的处理会影响后面的行为?
例如:
- 字符串解析(JSON、XML、表达式)
- 网络协议处理(HTTP、TCP)
- 词法分析器
1.4 有"非法"或"终止"条件
❓ 某些情况下需要立即停止处理?
❓ 有明确的"合法路径"和"非法路径"?
例如:
- 遇到非数字字符就停止
- 状态转换不合法就报错
2、快速判断清单
遇到问题时,问自己这些问题:
| 问题 | 如果答案是"是" | 示例 |
|---|---|---|
| 能否画出流程图? | 可能适合FSM | 订单状态流转 |
| 是否有if-else嵌套超过3层? | FSM可以简化 | 复杂的输入验证 |
| 是否在不同阶段做不同的事? | 典型FSM场景 | 登录流程 |
| 处理逻辑能否用"当前状态+输入→新状态"描述? | 完美匹配FSM | 游戏角色AI |
| 是否需要"记住"之前发生了什么? | 状态就是记忆 | 解析引号内的字符串 |
3、常见FSM题目模式 📝
模式1: 字符串解析类
关键词:解析、验证、转换、提取
题目:
- 字符串转整数 (atoi)
- 验证IP地址
- 简化路径
- 有效数字
- 正则表达式匹配
模式2: 括号/配对类
关键词:匹配、嵌套、平衡
题目:
- 有效的括号
- 最长有效括号
- 删除无效的括号
模式3: 状态转换类
关键词:状态、阶段、流程
题目:
- 股票买卖(持有/未持有状态)
- 打家劫舍(抢/不抢状态)
- 游戏胜负判断
模式4: 模式识别类
关键词:模式、序列、匹配
题目:
- KMP算法
- 字符串匹配
- 正则表达式
4、对比:何时不用FSM?
❌ 不适合FSM的情况:
- 需要回溯 - 如深度优先搜索
- 状态爆炸 - 状态数量指数增长
- 简单的线性逻辑 - 几个if就能解决
- 需要复杂计算 - 如数学公式求解
5、总结
核心判断标准:
- ✅ 能否用"状态+输入→新状态"描述?
- ✅ 是否有明确的处理阶段?
- ✅ 同样输入在不同阶段行为不同?
如果3个问题都是"是",那就是FSM的好场景!
三、leedCode实战
问题
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数。
函数 myAtoi(string s) 的算法如下:
- 空格: 读入字符串并丢弃无用的前导空格(
" ") - 符号: 检查下一个字符(假设还未到字符末尾)为
'-'还是'+'。如果两者都不存在,则假定结果为正。 - 转换: 通过跳过前置零来读取该整数,直到遇到非数字字符或到达字符串的结尾。如果没有读取数字,则结果为0。
- 舍入: 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1],需要截断这个整数,使其保持在这个范围内。具体来说,小于−231的整数应该被舍入为−231,大于231 − 1的整数应该被舍入为231 − 1。
解答
先画出状态机
根据状态机实现程序
`class Solution {
public int myAtoi(String s) {
int state = 0;
int flag = 1;
int result = 0;
for(char c : s.toCharArray()) {
switch(state) {
case 0:
if(c == ' ') state = 0;
else if(c == '+' || c == '-') {
state = 1;
flag = c == '+' ? 1 : -1;
} else if(isDigit(c)) {
state = 2;
result = c - '0';
} else {
state = 3;
}
break;
case 1:
if(isDigit(c)) {
state = 1;
result = result * 10 + (c - '0');
} else {
state = 3;
}
break;
case 2:
if(isDigit(c)) {
state = 2;
result = result * 10 + (c - '0');
} else {
state = 3;
}
break;
}
if(state == 3) {
return flag * result;
}
}
return flag * result;
}
public boolean isDigit(char c) {
return c >= '0' && c <= '9';
}