#65 字符串模拟 困难

有效数字

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

给定一个字符串 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. 最直接:用正则或语言内置解析(如 Python float(s))——面试/竞赛通常不允许,且手写正则极易漏边界(如 "4."、"-.9")。

2. 重复在哪里?每读一个字符,「当前已读前缀是否仍可能是有效数字的一部分」只取决于当前所处阶段(有没有符号、整数、小数点、指数等),与更早细节无关——适合有限状态机。

3. 拆成 8 个状态:起始(0) → 可选符号(1) → 整数(2) → 仅点(3) → 小数(4) → 指数 e(5) → 指数符号(6) → 指数整数(7)。合法结尾必须是 2、4 或 7(至少读过一位数字)。

4. 手推 "-.9":'-' 0→1,'.' 1→3,'9' 3→4,结束 state=4 ∈ VALID → true。手推 "1e":'1' 0→2,'e' 2→5,结束 state=5 ∉ VALID → false。

5. 也可用 seen_digit / seen_dot / seen_e 三旗标逐字符模拟,但状态机转移表更不易漏 case;本题困难主要体现在边界枚举完整。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
stateint定义:当前解析所处的 DFA 状态(0 起始 → 1 已见符号 → 2 整数 → 3 仅小数点 → 4 小数 → 5 已见 e → 6 指数符号 → 7 指数整数)
维护:每读一个字符,按转移表查下一状态;非法转移置 -1
更新:遍历完整个字符串后,state ∈ {2, 4, 7} 才为有效数字
TRANS[state]dict定义:状态转移表,键为字符类别(sign/digit/dot/e)
维护:编码题目规则——如状态 2 可读数字/小数点/指数,状态 3 只能接数字(".5" 型)
更新:只读常量,不随输入改变;当前字符映射到类别后查表得下一 state
cchar定义:当前扫描的字符
维护:按 +-、eE、.、数字四类分发到转移键
更新:从左到右逐字符处理,遇未知字符直接返回 false
VALIDset定义:合法终态集合 {2, 4, 7}(纯整数、小数、带指数)
维护:状态 1/3/5/6 不能作为结尾——如 "+"、"."、"1e"、"1e+" 均非法
更新:遍历结束后用 state in VALID 判定

⌨️ 落码步骤

1. 定义 8 行转移表 TRANS 与合法终态 VALID = {2, 4, 7}

2. state = 0,从左到右遍历 s 每个字符 c

3. 将 c 映射为类别:数字 → digit,+- → sign,. → dot,eE → e;未知字符返回 false

4. state = TRANS[state].get(类别, -1),若为 -1 说明非法转移,返回 false

5. 遍历结束后返回 state in VALID

💻 代码实现

class Solution:
    def isNumber(self, s: str) -> bool:
        # 状态: 0起始 1符号 2整数 3仅点 4小数 5指数e 6指数符号 7指数整数
        TRANS = [
            {'sign': 1, 'dot': 3, 'digit': 2},           # 0
            {'digit': 2, 'dot': 3},                       # 1
            {'digit': 2, 'dot': 4, 'e': 5},               # 2
            {'digit': 4},                                  # 3
            {'digit': 4, 'e': 5},                         # 4
            {'sign': 6, 'digit': 7},                       # 5
            {'digit': 7},                                  # 6
            {'digit': 7},                                  # 7
        ]
        VALID = {2, 4, 7}
        state = 0
        for c in s:
            if c.isdigit():
                kind = 'digit'
            elif c in '+-':
                kind = 'sign'
            elif c == '.':
                kind = 'dot'
            elif c in 'eE':
                kind = 'e'
            else:
                return False
            state = TRANS[state].get(kind, -1)
            if state == -1:
                return False
        return state in VALID
class Solution {
public:
    bool isNumber(string s) {
        // 状态: 0起始 1符号 2整数 3仅点 4小数 5指数e 6指数符号 7指数整数
        vector<unordered_map<string, int>> trans = {
            {{"sign",1}, {"dot",3}, {"digit",2}},
            {{"digit",2}, {"dot",3}},
            {{"digit",2}, {"dot",4}, {"e",5}},
            {{"digit",4}},
            {{"digit",4}, {"e",5}},
            {{"sign",6}, {"digit",7}},
            {{"digit",7}},
            {{"digit",7}},
        };
        unordered_set<int> valid = {2, 4, 7};
        int state = 0;
        for (char c : s) {
            string kind;
            if (isdigit(c)) kind = "digit";
            else if (c == '+' || c == '-') kind = "sign";
            else if (c == '.') kind = "dot";
            else if (c == 'e' || c == 'E') kind = "e";
            else return false;
            auto it = trans[state].find(kind);
            if (it == trans[state].end()) return false;
            state = it->second;
        }
        return valid.count(state);
    }
};
// 时间 O(n),空间 O(1)

📈 复杂度分析

时间复杂度 O(n)
空间复杂度 O(1)

⚠️ 常见坑

❗ 终态判断遗漏:仅 "+"、"."、"1e"、"1e+" 等中间态不能返回 true,必须 state ∈ {2,4,7}。

❗ 指数后不能再有小数点:"99e2.5" 在读到 . 时状态 7 无 dot 转移,应判 false。

❗ 符号位置错误:"--6"、"-+3" 在状态 2 遇到第二个 sign 无转移;指数符号只能在状态 5 后出现,不能出现在整数中间。

🔍 必测边界 Case

Case 1:单字符合法
"0" → true(示例 1,纯整数)
Case 2:仅指数符号
"e" → false(示例 2,无任何数字)
Case 3:仅小数点
"." → false(示例 3,点后无数字)
Case 4:点前无整数
"-.9" → true,"+.5" → true(符号后直接小数点再接数字)
Case 5:整数后单独小数点
"4." → true(状态 2 遇 . 进入 4,无需小数部分)
Case 6:指数缺整数
"1e" → false,"1e+" → false(指数部分必须有至少一位数字)