Hard
题目描述
给定一个字符串 s,返回 s 是否是一个有效数字。
例如,以下都是有效数字:“2”, “0089”, “-0.1”, “+3.14”, “4.”, “-.9”, “2e10”, “-90E3”, “3e+7”, “+6e-1”, “53.5e93”, “-123.456e789”,而以下不是有效数字:“abc”, “1a”, “1e”, “e3”, “99e2.5”, “–6”, “-+3”, “95a54e53”。
形式上,有效数字可以用以下定义之一来定义:
- 一个整数后跟一个可选的指数部分
- 一个小数后跟一个可选的指数部分
整数定义为可选的符号 ‘-’ 或 ‘+’ 后跟数字。
小数定义为可选的符号 ‘-’ 或 ‘+’ 后跟以下定义之一:
- 数字后跟一个点 ‘.’
- 数字后跟一个点 ‘.’ 再跟数字
- 一个点 ‘.’ 后跟数字
指数定义为指数符号 ’e’ 或 ‘E’ 后跟一个整数。
数字定义为一个或多个数字。
示例 1:
输入:s = "0"
输出:true
示例 2:
输入:s = "e"
输出:false
示例 3:
输入:s = "."
输出:false
提示:
1 <= s.length <= 20s仅包含英文字母(大写和小写)、数字(0-9)、加号 ‘+’、减号 ‘-’ 或点 ‘.’
解题思路
这道题可以用状态机或者直接解析两种方法来解决。
状态机方法:定义不同的状态来表示解析过程中的不同阶段(符号、数字、小数点、指数等),通过状态转移来判断是否为有效数字。
直接解析方法:按照数字的结构特点,将字符串分为三个部分进行验证:
- 基数部分(小数点前的整数或小数)
- 指数标志(e/E)
- 指数部分(e/E后的整数)
推荐使用直接解析方法,因为逻辑更清晰易懂。具体步骤:
- 先处理可选的正负号
- 解析基数部分,可能包含数字和最多一个小数点
- 如果遇到 e/E,则解析指数部分
- 指数部分必须是整数(可以有正负号)
- 确保各部分都有有效数字
关键点:
- 小数点前后至少要有一个数字
- 指数部分必须是完整的整数
- 不能有多余的字符
代码实现
class Solution {
public:
bool isNumber(string s) {
int i = 0;
int n = s.length();
// 跳过开头的正负号
if (i < n && (s[i] == '+' || s[i] == '-')) {
i++;
}
bool hasDigit = false;
bool hasDot = false;
// 解析基数部分(可能包含小数点)
while (i < n && (isdigit(s[i]) || s[i] == '.')) {
if (s[i] == '.') {
if (hasDot) return false; // 多个小数点
hasDot = true;
} else {
hasDigit = true;
}
i++;
}
// 基数部分必须至少有一个数字
if (!hasDigit) return false;
// 如果有指数部分
if (i < n && (s[i] == 'e' || s[i] == 'E')) {
i++;
// 指数部分的正负号
if (i < n && (s[i] == '+' || s[i] == '-')) {
i++;
}
// 指数部分必须有数字
bool hasExpDigit = false;
while (i < n && isdigit(s[i])) {
hasExpDigit = true;
i++;
}
if (!hasExpDigit) return false;
}
// 确保没有多余字符
return i == n;
}
};
class Solution:
def isNumber(self, s: str) -> bool:
i = 0
n = len(s)
# 跳过开头的正负号
if i < n and s[i] in '+-':
i += 1
has_digit = False
has_dot = False
# 解析基数部分(可能包含小数点)
while i < n and (s[i].isdigit() or s[i] == '.'):
if s[i] == '.':
if has_dot:
return False # 多个小数点
has_dot = True
else:
has_digit = True
i += 1
# 基数部分必须至少有一个数字
if not has_digit:
return False
# 如果有指数部分
if i < n and s[i] in 'eE':
i += 1
# 指数部分的正负号
if i < n and s[i] in '+-':
i += 1
# 指数部分必须有数字
has_exp_digit = False
while i < n and s[i].isdigit():
has_exp_digit = True
i += 1
if not has_exp_digit:
return False
# 确保没有多余字符
return i == n
public class Solution {
public bool IsNumber(string s) {
int i = 0;
int n = s.Length;
// 跳过开头的正负号
if (i < n && (s[i] == '+' || s[i] == '-')) {
i++;
}
bool hasDigit = false;
bool hasDot = false;
// 解析基数部分(可能包含小数点)
while (i < n && (char.IsDigit(s[i]) || s[i] == '.')) {
if (s[i] == '.') {
if (hasDot) return false; // 多个小数点
hasDot = true;
} else {
hasDigit = true;
}
i++;
}
// 基数部分必须至少有一个数字
if (!hasDigit) return false;
// 如果有指数部分
if (i < n && (s[i] == 'e' || s[i] == 'E')) {
i++;
// 指数部分的正负号
if (i < n && (s[i] == '+' || s[i] == '-')) {
i++;
}
// 指数部分必须有数字
bool hasExpDigit = false;
while (i < n && char.IsDigit(s[i])) {
hasExpDigit = true;
i++;
}
if (!hasExpDigit) return false;
}
// 确保没有多余字符
return i == n;
}
}
var isNumber = function(s) {
let i = 0;
const n = s.length;
// 跳过开头的正负号
if (i < n && (s[i]
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历字符串一次,n为字符串长度 |
| 空间复杂度 | O(1) | 只使用了常数个变量 |
相关题目
- . String to Integer (atoi) (Medium)