剑指 Offer 20. 表示数值的字符串
剑指 Offer 20. 表示数值的字符串
难度中等
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"+-5"及"12e+5.4"都不是。
函数签名:
func isNumber(s string) bool
分析
题目描述非常模糊,到底什么样的字符串能表示数值只举了几个例子,并没有给出精确规范。查看题解,精确规范如下:
在 C++ 文档中,描述了一个合法的数值字符串应当具有的格式。具体而言,它包含以下部分:
符号位,即 +、− 两种符号
整数部分,即由若干字符 0−9 组成的字符串
小数点
小数部分,其构成与整数部分相同
指数部分,其中包含开头的字符 e(大写小写均可)、可选的符号位,和整数部分
相比于 C++ 文档而言,本题还有一点额外的不同,即允许字符串首末两端有一些额外的空格。
在上面描述的五个部分中,每个部分都不是必需的,但也受一些额外规则的制约,如:
如果符号位存在,其后面必须跟着数字或小数点。
小数点的前后两侧,至少有一侧是数字。
朴素实现
首尾空格可以事先去除。一个数字字符串包含符号、数字、e、小数点四种元素,可以用 4 个 bool 变量维护这四种元素是否已经出现过,在遍历过程中发现违反规则的情况直接返回 false。
func isNumber(s string) bool {
s = strings.TrimSpace(s)
if len(s) == 0 {
return false
}
var signSeen, digitSeen, eSeen, dotSeen bool
for _, v := range s {
switch {
case v == '+' || v == '-':
// 全局最多一个符号、且必须在数字和小数点之前。(注意允许在 e 或 E 之后,e 或 E 比较特殊)
if signSeen || digitSeen || dotSeen {
return false
}
signSeen = true
case v >= '0' && v <= '9':
// 任意位置都可以出现数字
digitSeen = true
case v == 'e' || v == 'E':
// e/E 最多只能出现一次,且之前必须有数字
if eSeen || !digitSeen {
return false
}
// 注意这里把符号、小数点、数字标识重置为 false
signSeen, dotSeen, digitSeen = false, false, false
eSeen = true
case v == '.':
// 小数点最多只能出现一次,且不能在 e/E 之后
if dotSeen || eSeen {
return false
}
dotSeen = true
default:
// 其他非法字符
return false
}
}
// 至少要有一个数字
return digitSeen
}
时间复杂度 O(n), 空间复杂度 O(1)
有限状态机
这是一个更一般的思路。
type CharType int
const (
CharIllegal CharType = iota
CharExp
CharPoint
CharSign
CharNumber
)
const charTypeCnt = 5
func toCharType(ch byte) CharType {
switch ch {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return CharNumber
case 'e', 'E':
return CharExp
case '.':
return CharPoint
case '+', '-':
return CharSign
}
return CharIllegal
}
type State int
const (
StateIllegal State = iota
StateInitial
StateIntSign // 整数部分的符号
StateInteger // 正数部分的数字
StatePoint
StatePointWithoutInt // 没有正数部分的小数点
StateFraction // 小数部分
StateExp // 指数部分
StateExpSign // 指数部分的符号
StateExpNumber // 指数部分的数字
)
const stateCnt = 10
var transfer = [stateCnt][charTypeCnt]State{
StateInitial: { // 可以以数字、小数点、符号开始
CharNumber: StateInteger,
CharPoint: StatePointWithoutInt,
CharSign: StateIntSign,
},
StateInteger: {
CharNumber: StateInteger,
CharExp: StateExp,
CharPoint: StatePoint,
},
StatePoint: {
CharNumber: StateFraction,
CharExp: StateExp,
},
StateIntSign: {
CharNumber: StateInteger,
CharPoint: StatePointWithoutInt,
},
StatePointWithoutInt: {
CharNumber: StateFraction,
},
StateFraction: {
CharNumber: StateFraction,
CharExp: StateExp,
},
StateExp: {
CharNumber: StateExpNumber,
CharSign: StateExpSign,
},
StateExpSign: {
CharNumber: StateExpNumber,
},
StateExpNumber: {
CharNumber: StateExpNumber,
},
}
func isNumber(s string) bool {
s = strings.TrimSpace(s)
state := StateInitial
for i := 0; i < len(s); i++ {
charType := toCharType(s[i])
if transfer[state][charType] == StateIllegal {
return false
}
state = transfer[state][charType]
}
return state == StateInteger || state == StatePoint || state == StateFraction || state == StateExpNumber
}
时间复杂度 O(n), 空间复杂度 O(1)