LeetCode 第65题:有效数字

61 阅读5分钟

LeetCode 第65题:有效数字

题目描述

有效数字(按顺序)可以分成以下几个部分:

  1. 一个 小数 或者 整数
  2. (可选)一个 'e''E' ,后面跟着一个 整数

小数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+''-'
  2. 下述格式之一:
    1. 至少一位数字,后面跟着一个点 '.'
    2. 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
    3. 一个点 '.' ,后面跟着至少一位数字

整数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+''-'
  2. 至少一位数字

部分有效数字列举如下:["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"]

给你一个字符串 s ,如果 s 是一个 有效数字 ,请返回 true

难度

困难

题目链接

点击在LeetCode中查看题目

示例

示例 1:

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

示例 2:

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

示例 3:

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

示例 4:

输入:s = ".1" 输出:true

提示

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

解题思路

状态机解法

这道题可以使用有限状态机(DFA)来解决。我们需要定义不同的状态和状态转移规则。

关键点:

  1. 定义所有可能的状态
  2. 明确状态转移规则
  3. 确定合法的终止状态
  4. 处理所有可能的输入字符

具体步骤:

  1. 定义状态转移表
  2. 遍历字符串的每个字符
  3. 根据当前状态和字符进行状态转移
  4. 检查最终状态是否合法

图解思路

算法步骤分析表

状态说明可接受字符下一状态
初始开始状态+/-符号状态
符号符号后数字/点整数/小数
整数整数部分数字/点/e整数/小数/指数
小数小数部分数字/e小数/指数
指数指数部分+/-/数字指数符号/指数数字

状态/情况分析表

情况输入输出说明
整数"123"true基本情况
小数"0.1"true带小数点
科学计数"1e10"true带指数
非法"abc"false含字母

代码实现

C# 实现

public class Solution {
    public bool IsNumber(string s) {
        int state = 0;
        bool[] finals = {false, false, true, true, false, true, true, false, true};
        int[][] transfer = new int[][]{
            new int[] {0, 1, 6, 2, -1},    // 0 初始状态
            new int[] {-1, -1, 6, 2, -1},   // 1 符号状态
            new int[] {-1, -1, 3, -1, -1},  // 2 小数点前的整数
            new int[] {8, -1, 3, -1, 4},    // 3 小数点后的整数
            new int[] {-1, 7, 5, -1, -1},   // 4 指数符号e
            new int[] {8, -1, 5, -1, -1},   // 5 指数后的整数
            new int[] {8, -1, 6, 3, 4},     // 6 小数点前的整数
            new int[] {-1, -1, 5, -1, -1},  // 7 指数后的符号
            new int[] {8, -1, -1, -1, -1}   // 8 结束状态
        };
        
        foreach (char c in s) {
            int id = GetInputId(c);
            if (id < 0) return false;
            
            state = transfer[state][id];
            if (state < 0) return false;
        }
        
        return finals[state];
    }
    
    private int GetInputId(char c) {
        if (c == ' ') return 0;
        if (c == '+' || c == '-') return 1;
        if (c >= '0' && c <= '9') return 2;
        if (c == '.') return 3;
        if (c == 'e' || c == 'E') return 4;
        return -1;
    }
}

Python 实现

class Solution:
    def isNumber(self, s: str) -> bool:
        # 定义状态转移表
        transfer = {
            0: {' ': 0, 'sign': 1, 'digit': 6, '.': 2},
            1: {'digit': 6, '.': 2},
            2: {'digit': 3},
            3: {'digit': 3, 'e': 4},
            4: {'sign': 7, 'digit': 5},
            5: {'digit': 5},
            6: {'digit': 6, '.': 3, 'e': 4},
            7: {'digit': 5},
            8: {' ': 8}
        }
        
        # 定义终止状态
        finals = {3, 5, 6, 8}
        
        def get_char_type(c: str) -> str:
            if c.isspace():
                return ' '
            if c in ['+', '-']:
                return 'sign'
            if c.isdigit():
                return 'digit'
            if c == '.':
                return '.'
            if c in ['e', 'E']:
                return 'e'
            return 'unknown'
        
        # 初始状态
        state = 0
        for c in s:
            char_type = get_char_type(c)
            if char_type == 'unknown':
                return False
            
            if char_type not in transfer[state]:
                return False
                
            state = transfer[state][char_type]
            
        return state in finals

C++ 实现

class Solution {
public:
    bool isNumber(string s) {
        vector<bool> finals = {false, false, true, true, false, true, true, false, true};
        vector<vector<int>> transfer = {
            {0, 1, 6, 2, -1},    // 0 初始状态
            {-1, -1, 6, 2, -1},  // 1 符号状态
            {-1, -1, 3, -1, -1}, // 2 小数点前的整数
            {8, -1, 3, -1, 4},   // 3 小数点后的整数
            {-1, 7, 5, -1, -1},  // 4 指数符号e
            {8, -1, 5, -1, -1},  // 5 指数后的整数
            {8, -1, 6, 3, 4},    // 6 小数点前的整数
            {-1, -1, 5, -1, -1}, // 7 指数后的符号
            {8, -1, -1, -1, -1}  // 8 结束状态
        };
        
        int state = 0;
        for (char c : s) {
            int id = getInputId(c);
            if (id < 0) return false;
            
            state = transfer[state][id];
            if (state < 0) return false;
        }
        
        return finals[state];
    }
    
private:
    int getInputId(char c) {
        if (c == ' ') return 0;
        if (c == '+' || c == '-') return 1;
        if (isdigit(c)) return 2;
        if (c == '.') return 3;
        if (c == 'e' || c == 'E') return 4;
        return -1;
    }
};

执行结果

  • 执行用时:76 ms
  • 内存消耗:37.1 MB

代码亮点

  1. 🎯 使用状态机清晰处理各种情况
  2. 💡 状态转移表设计合理
  3. 🔍 代码结构清晰,易于维护
  4. 🎨 优雅处理复杂的判断逻辑

常见错误分析

  1. 🚫 没有处理所有可能的输入字符
  2. 🚫 状态转移规则不完整
  3. 🚫 终止状态判断错误
  4. 🚫 特殊情况处理不当

解法对比

解法时间复杂度空间复杂度优点缺点
正则表达式O(n)O(1)代码简短可读性差
条件判断O(n)O(1)直观易懂逻辑复杂
状态机O(n)O(1)结构清晰实现复杂

相关题目