【解题思路】
本题使用有限状态自动机。根据字符类型和合法数值的特点,先定义状态,再画出状态转移图,最后编写代码即可。
字符类型:
空格 「 」、数字「 0—9 」 、正负号 「 + - 」 、小数点 「 … 」 、幂符号 「 eE 」 。
状态定义:
首先我们得知道什么样的字符串是数值
假设字符串为A.BeC或A.BEC, 也就是整数部分为A,小数部分为B,指数部分为C,按顺序判断是否包含这三部分。
按照字符串从左到右的顺序,定义以下 9 种状态。
0. 开始的空格
1. 幂符号前的正负号
2. 小数点前的数字
3. 小数点、小数点后的数字
4. 当小数点前为空格时,小数点、小数点后的数字(意思是没有正数部分,直接以小数点开始)
5. 幂符号(过渡状态)
6. 幂符号后的正负号(过渡状态)
7. 幂符号后的数字
8. 结尾的空格
.
其中,合法的结束状态有 2, 3, 7, 8
【状态2的示例】
- 13(即13.0 小数点后面 可以没有数字),
- +5或-6(从0状态到2状态 无正负号,从1到2状态 可以有+/-)
- 错误示例 +-7,一个数字前只能有一个+/-,可以看到 在states[p]=states[2]中找sign,是不存在的,也可以视为越界
【状态3的示例】.14(即0.14 小数点前面 可以没有数字)
【状态7的示例】
- e5,幂符号后只能有纯数字,不能有小数点/±
- 错误示例 e5.4或e+5.4
乍一看,有点懵,先看 状态转移图 捋一捋
定义状态的代码如下:
states = [
{ ' ': 0, 's': 1, 'd': 2, '.': 4 }, # 0. start with 'blank'
{ 'd': 2, '.': 4 } , # 1. 'sign' before 'e'
{ 'd': 2, '.': 3, 'e': 5, ' ': 8 }, # 2. 'digit' before 'dot'
{ 'd': 3, 'e': 5, ' ': 8 }, # 3. 'digit' after 'dot'
{ 'd': 3 }, # 4. 'digit' after 'dot' (‘blank’ before 'dot')
{ 's': 6, 'd': 7 }, # 5. 'e'
{ 'd': 7 }, # 6. 'sign' after 'e'
{ 'd': 7, ' ': 8 }, # 7. 'digit' after 'e'
{ ' ': 8 } # 8. end with 'blank'
]
-
什么意思?
==> 这是一个状态列表,每个元素是字典,key值是 判断当前字符所属类型,value值 是下一个跳转的状态编号例如:从空格状态0,获得一个数字,属于数字 ‘d’,跳转到状态2
例如:从状态2,获得一个小数点,属于小数点 ‘.’,跳转到状态3有点类似二维数组 states[p][t] 这样,描述一个新状态,作为新的p
-
那么怎么判断 当前字符所属类型?
if '0' <= c <= '9': # 一组if结构即可判断 t = 'd' # digit elif c in "+-": t = 's' # sign elif c in "eE": t = 'e' # e or E elif c in ". ": t = c # dot, blank else: t = '?' # unknown
- 复杂度分析:
- 时间复杂度 O(N) : 其中 N 为字符串 s 的长度,判断需遍历字符串,每轮状态转移的使用 O(1) 时间。
- 空间复杂度 O(1): states 和 p 使用常数大小的额外空间。
假设输入字符串为‘5.2e-3’,则判断过程如下
第四个字符e,是幂符号e,跳到状态5
第五个字符-,是正负号sign,跳到状态6
第六个字符3,是digit,跳到状态7
遍历完字符串,跳出循环,最后一个状态是7,属于合法状态,返回True
完整代码如下
class Solution:
def isNumber(self, s: str) -> bool:
states = [
{ ' ': 0, 's': 1, 'd': 2, '.': 4 }, # 0. start with 'blank'
{ 'd': 2, '.': 4 } , # 1. 'sign' before 'e'
{ 'd': 2, '.': 3, 'e': 5, ' ': 8 }, # 2. 'digit' before 'dot'
{ 'd': 3, 'e': 5, ' ': 8 }, # 3. 'digit' after 'dot'
{ 'd': 3 }, # 4. 'digit' after 'dot' (‘blank’ before 'dot')
{ 's': 6, 'd': 7 }, # 5. 'e'
{ 'd': 7 }, # 6. 'sign' after 'e'
{ 'd': 7, ' ': 8 }, # 7. 'digit' after 'e'
{ ' ': 8 } # 8. end with 'blank'
]
p = 0 # start with state 0
for c in s: # 遍历字符串
if '0' <= c <= '9': t = 'd' # digit
elif c in "+-": t = 's' # sign
elif c in "eE": t = 'e' # e or E
elif c in ". ": t = c # dot, blank
else: t = '?' # unknown
if t not in states[p]: return False
p = states[p][t]
return p in (2, 3, 7, 8)
'''
太赞了!
作者:jyd
链接:https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/solution/mian-shi-ti-20-biao-shi-shu-zhi-de-zi-fu-chuan-y-2/
'''
状态states 长度为: 9
字符串s 的总长度为: 6
当前字符c=5 属于 数字digit
当前状态p = 0 记录字符t = d 记为states[0][d] = 2
故得到新状态p = 2 跳转到states[2]的字典中 找下一个状态
当前字符c = . 属于 小数点.dot
当前状态p = 2 记录字符t = . 记为states[2][.] = 3
故得到新状态p = 3 跳转到states[3]的字典中 找下一个状态
当前字符c=2 属于 数字digit
当前状态p = 3 记录字符t = d 记为states[3][d] = 3
故得到新状态p = 3 跳转到states[3]的字典中 找下一个状态
当前字符c = e 属于 幂符号e
当前状态p = 3 记录字符t = e 记为states[3][e] = 5
故得到新状态p = 5 跳转到states[5]的字典中 找下一个状态
当前字符c = - 属于 正负号sign
当前状态p = 5 记录字符t = s 记为states[5][s] = 6
故得到新状态p = 6 跳转到states[6]的字典中 找下一个状态
当前字符c=3 属于 数字digit
当前状态p = 6 记录字符t = d 记为states[6][d] = 7
故得到新状态p = 7 跳转到states[7]的字典中 找下一个状态
True
【打印版】
def isNumber(s):
states = [
{ ' ': 0, 's': 1, 'd': 2, '.': 4 }, # 0. start with 'blank'
{ 'd': 2, '.': 4 } , # 1. 'sign' before 'e'
{ 'd': 2, '.': 3, 'e': 5, ' ': 8 }, # 2. 'digit' before 'dot'
{ 'd': 3, 'e': 5, ' ': 8 }, # 3. 'digit' after 'dot'
{ 'd': 3 }, # 4. 'digit' after 'dot' (‘blank’ before 'dot')
{ 's': 6, 'd': 7 }, # 5. 'e'
{ 'd': 7 }, # 6. 'sign' after 'e'
{ 'd': 7, ' ': 8 }, # 7. 'digit' after 'e'
{ ' ': 8 } # 8. end with 'blank'
]
print('状态states 长度为:',len(states))
p = 0 # start with state 0
print('字符串s 的总长度为:',len(s))
for c in s: # 遍历字符串
if '0' <= c <= '9': # 判断 当前字符所属类型
t = 'd' # digit
print('当前字符c=%s\t属于 数字digit'%c)
elif c in "+-":
t = 's' # sign
print('当前字符c = %s\t属于 正负号sign'%c)
elif c in "eE":
t = 'e' # e or E
print('当前字符c = %s\t属于 幂符号e'%c)
elif c in ". ":
t = c # dot, blank
print('当前字符c = %s\t属于 小数点.dot'%c)
else:
t = '?' # unknown
print('当前字符c = %s\tUnknown'%c)
if t not in states[p]:
return False
print('当前状态p =',p,end='\t')
print('记录字符t =',t,end='\t')
print('记为states[%s][%s] = %d'%(p,t,states[p][t]))
p = states[p][t]
print('故得到新状态p =',p,end='\t')
print('跳转到states[%s]的字典中 找下一个状态\n'%p)
return p in (2, 3, 7, 8)
if __name__ == "__main__":
s = '5.2e-3'
print(isNumber(s))
关于Leetcode用户@白露塞纳的几个疑问
- 为什么状态3和4中的小数点不独立出来作为一个状态呢?
- 状态4->3不太理解,为什么这样转?
- 状态1->4 【如“ + .1" , return false】 为什么可以?
- 状态1->3 【如 “+.1” , return true】
4->5 【如 “+.1e2” , return true】
4->8 【如 ".1 " , return true】
为什么不可以?.
我的解答是如下:
- 状态3和4其实是一样的,区别是状态3是间接到达,是过渡阶段;状态4是直接到达。
独立一个状态其实也是可以的,看这张草图
- 状态4到3,等同与状态3到3,都是小数点后 获得数字的 状态。
- 字符串‘+.1’ 的状态转化为:0-1-4-3,按照图上画的,也都是可以转化的,return True,是表示数值
- 字符串’+.1e2’ 的状态转化为:0-1-4-3-5-7,正确转化,return True,是表示数值
字符串‘.1’ 的转化状态为:0-4-3,正确转化,return True,是表示数值.
我也有个疑问是:为什么8是合法的结束状态呢?为什么不是到7就停止了?