LeetCode 65. 有效数字
题目描述
✅ 65. 有效数字


题意分析
判断整个字符串是否符合题目定义的十进制数字格式。主体可以是带可选正负号的整数或小数,也可以在后面追加由
e或E引导的指数;指数部分只能是带可选正负号的整数。主体的小数点最多一个,点前或点后允许一侧没有数字,但主体整体必须至少含一个数字。若出现指数符号,它后面也必须至少有一个数字,不能只有符号。题目验证的是整串语法,不是读取合法前缀,也不要求把它转换为某种有限范围的数值类型。
解法:一次遍历状态判断
核心思路
[!blue]
逐个字符扫描,只需保存三个状态:
seenDigit表示当前部分已经出现数字,seenDot表示主体已经出现小数点,seenExp表示已经进入指数部分。数字、点、指数符号和正负号分别按自己的位置限制检查,其余字符直接失败。遇到数字时,把
seenDigit设为真。遇到小数点时,要求此前没有点且还没有指数;点本身不算数字,因此只设置seenDot,不改变数字标记。这样可以允许点出现在主体数字之前或之后,同时仍能在最后拒绝完全没有数字的主体。遇到
e或E时,要求这是第一次指数符号,且此前的主体已经有数字。检查通过后设置seenExp,并将seenDigit清空:主体已确认完整,接下来同一个标记改为记录指数部分是否出现数字。因为指数中不允许小数点,之后的点会由seenExp直接拒绝。正负号只允许位于整个字符串开头,或紧跟指数符号之后。检查它的下标和前一个字符就足够了:第二个连续符号的前一位不是指数符号,因此会被拒绝,无需再保存独立的符号计数。
任何局部规则被违反就立即返回假。扫描结束后返回
seenDigit:没有指数时,它保证主体含数字;有指数时,它保证指数也已补上数字。结合扫描中对重复点、重复指数、符号位置和非法字符的限制,就覆盖了完整格式要求。
解题步骤
- 将数字、小数点、指数三个标记初始化为假。
- 遇到数字就设置当前部分的数字标记。
- 遇到点则检查尚无点、尚无指数;遇到指数则检查主体已有数字且此前没有指数,再清空数字标记。
- 遇到正负号则检查它是否在开头或紧随指数;其他字符直接拒绝。
- 全部字符处理完后,返回当前部分是否已有数字。
代码实现
class Solution {
// 小数点只能出现在底数部分,且最多出现一次。
public boolean isNumber(String s) {
boolean seenDigit = false;
boolean seenDot = false;
boolean seenExp = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
seenDigit = true;
} else if (c == '.') {
if (seenDot || seenExp) {
return false;
}
seenDot = true;
} else if (c == 'e' || c == 'E') {
// 进入指数前必须已有底数数字,且指数只能出现一次
if (seenExp || !seenDigit) {
return false;
}
seenExp = true;
// 底数已经验证,重新记录指数部分是否出现数字
seenDigit = false;
} else if (c == '+' || c == '-') {
if (i > 0 && s.charAt(i - 1) != 'e' && s.charAt(i - 1) != 'E') {
return false;
}
} else {
return false;
}
}
// 最后一段必须有数字,单独的符号或点不能通过
return seenDigit;
}
}
func isNumber(s string) bool {
// 小数点只能出现在底数部分,且最多出现一次。
seenDigit := false
seenDot := false
seenExp := false
for i := 0; i < len(s); i++ {
c := s[i]
if c >= '0' && c <= '9' {
seenDigit = true
} else if c == '.' {
if seenDot || seenExp {
return false
}
seenDot = true
} else if c == 'e' || c == 'E' {
// 进入指数前必须已有底数数字,且指数只能出现一次
if seenExp || !seenDigit {
return false
}
seenExp = true
// 底数已经验证,重新记录指数部分是否出现数字
seenDigit = false
} else if c == '+' || c == '-' {
if i > 0 && s[i-1] != 'e' && s[i-1] != 'E' {
return false
}
} else {
return false
}
}
// 最后一段必须有数字,单独的符号或点不能通过
return seenDigit
}
复杂度分析
- 时间复杂度:$O(n)$,每个字符只检查一次,单次判断为常数时间。
- 空间复杂度:$O(1)$,只使用三个布尔状态和扫描下标,不构造子串或解析数值。
关键点总结
[!green]
- 数字标记随主体、指数阶段切换,点和指数标记则记录全串已发生的结构。
- 指数出现时先确认主体,再清空数字标记等待指数数字。
- 扫描检查字符能否出现在当前位置,结尾再检查最后部分是否完整。
易错点总结
[!yellow]
- 进入指数后不清空数字标记,会让主体中的数字掩盖指数缺少数字的问题。
- 要求小数点前必须已有数字,或要求点后必须有数字,会错误拒绝题目允许的一侧缺省形式。
- 只检查小数点是否重复,没有检查是否进入指数,会接受带小数指数的错误格式。
- 允许正负号出现在任意位置,会破坏主体或指数内部的连续数字结构。
- 一遇到不认识的字符就返回前面部分的解析结果,变成了前缀转换,而本题要求整串有效。
- 将字符串转换为浮点数是否成功作为唯一依据,会混入语言库自己的语法和数值范围限制。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 8. 字符串转换整数 (atoi) | 中等 | 原题按atoi规则解析合法前缀,本题验证整串数值格式且允许小数和指数,停止规则不同。 |
| 468. 验证IP地址 | 中等 | 同样需要严格的整串语法校验,IP题按段检查,本题维护符号、小数点和指数位置规则。 |