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”。

形式上,有效数字可以用以下定义之一来定义:

  1. 一个整数后跟一个可选的指数部分
  2. 一个小数后跟一个可选的指数部分

整数定义为可选的符号 ‘-’ 或 ‘+’ 后跟数字。

小数定义为可选的符号 ‘-’ 或 ‘+’ 后跟以下定义之一:

  1. 数字后跟一个点 ‘.’
  2. 数字后跟一个点 ‘.’ 再跟数字
  3. 一个点 ‘.’ 后跟数字

指数定义为指数符号 ’e’ 或 ‘E’ 后跟一个整数。

数字定义为一个或多个数字。

示例 1:

输入:s = "0"
输出:true

示例 2:

输入:s = "e"
输出:false

示例 3:

输入:s = "."
输出:false

提示:

  • 1 <= s.length <= 20
  • s 仅包含英文字母(大写和小写)、数字(0-9)、加号 ‘+’、减号 ‘-’ 或点 ‘.’

解题思路

这道题可以用状态机或者直接解析两种方法来解决。

状态机方法:定义不同的状态来表示解析过程中的不同阶段(符号、数字、小数点、指数等),通过状态转移来判断是否为有效数字。

直接解析方法:按照数字的结构特点,将字符串分为三个部分进行验证:

  1. 基数部分(小数点前的整数或小数)
  2. 指数标志(e/E)
  3. 指数部分(e/E后的整数)

推荐使用直接解析方法,因为逻辑更清晰易懂。具体步骤:

  1. 先处理可选的正负号
  2. 解析基数部分,可能包含数字和最多一个小数点
  3. 如果遇到 e/E,则解析指数部分
  4. 指数部分必须是整数(可以有正负号)
  5. 确保各部分都有有效数字

关键点:

  • 小数点前后至少要有一个数字
  • 指数部分必须是完整的整数
  • 不能有多余的字符

代码实现

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)只使用了常数个变量

相关题目