有效数字

标签: 字符串

难度: Medium

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

  1. 若干空格
  2. 一个 小数 或者 整数
  3. (可选)一个 'e''E' ,后面跟着一个 整数
  4. 若干空格

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

  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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

Submission

运行时间: 404 ms

内存: 15 MB

from enum import Enum, auto


class Solution:
    def isNumber(self, s: str) -> bool:
        class State(Enum):
            STATE_INITIAL = auto()
            STATE_INT_SIGN = auto()
            STATE_INTEGER = auto()
            STATE_POINT = auto()
            STATE_POINT_WITHOUT_INT = auto()
            STATE_FRACTION = auto()
            STATE_EXP = auto()
            STATE_EXP_SIGN = auto()
            STATE_EXP_NUMBER = auto()
            STATE_END = auto()

        class CharType(Enum):
            CHAR_NUMBER = auto()
            CHAR_EXP = auto()
            CHAR_POINT = auto()
            CHAR_SIGN = auto()
            CHAR_SPACE = auto()
            CHAR_ILLEGAL = auto()

        def toCharType(ch):
            if ch.isdigit():
                return CharType.CHAR_NUMBER
            elif ch.lower() == "e":
                return CharType.CHAR_EXP
            elif ch == ".":
                return CharType.CHAR_POINT
            elif ch == "+" or ch == "-":
                return CharType.CHAR_SIGN
            elif ch == " ":
                return CharType.CHAR_SPACE
            else:
                return CharType.CHAR_ILLEGAL

        transfer = {
            State.STATE_INITIAL: {
                CharType.CHAR_SPACE: State.STATE_INITIAL,
                CharType.CHAR_NUMBER: State.STATE_INTEGER,
                CharType.CHAR_POINT: State.STATE_POINT_WITHOUT_INT,
                CharType.CHAR_SIGN: State.STATE_INT_SIGN,
            },
            State.STATE_INT_SIGN: {
                CharType.CHAR_NUMBER: State.STATE_INTEGER,
                CharType.CHAR_POINT: State.STATE_POINT_WITHOUT_INT,
            },
            State.STATE_INTEGER: {
                CharType.CHAR_NUMBER: State.STATE_INTEGER,
                CharType.CHAR_EXP: State.STATE_EXP,
                CharType.CHAR_POINT: State.STATE_POINT,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_POINT: {
                CharType.CHAR_NUMBER: State.STATE_FRACTION,
                CharType.CHAR_EXP: State.STATE_EXP,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_POINT_WITHOUT_INT: {
                CharType.CHAR_NUMBER: State.STATE_FRACTION,
            },
            State.STATE_FRACTION: {
                CharType.CHAR_NUMBER: State.STATE_FRACTION,
                CharType.CHAR_EXP: State.STATE_EXP,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_EXP: {
                CharType.CHAR_NUMBER: State.STATE_EXP_NUMBER,
                CharType.CHAR_SIGN: State.STATE_EXP_SIGN,
            },
            State.STATE_EXP_SIGN: {
                CharType.CHAR_NUMBER: State.STATE_EXP_NUMBER,
            },
            State.STATE_EXP_NUMBER: {
                CharType.CHAR_NUMBER: State.STATE_EXP_NUMBER,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_END: {
                CharType.CHAR_SPACE: State.STATE_END,
            },
        }

        st = State.STATE_INITIAL
        for ch in s:
            char_type = toCharType(ch)
            if char_type not in transfer[st]:
                return False
            st = transfer[st][char_type]

        return st in [State.STATE_INTEGER, State.STATE_POINT, State.STATE_FRACTION, 
                      State.STATE_EXP_NUMBER, State.STATE_END]

Explain

该题解使用了有限状态自动机(Finite State Machine, FSM)的方法来解决问题。首先,定义了不同的状态(State)和字符类型(CharType)。然后,根据题目给出的有效数字的定义,构建了状态转移图,其中包含了各个状态下对应字符类型的下一个状态。最后,遍历输入字符串,根据当前字符的类型和当前状态,进行状态转移,如果遇到非法字符或者无法转移的状态,则返回false。最终,如果字符串遍历完成后,状态机处于接受状态(即整数、小数、指数部分的数字、或者末尾空格状态),则返回true,表示字符串是一个有效数字。

时间复杂度: O(n)

空间复杂度: O(1)

from enum import Enum, auto


class Solution:
    def isNumber(self, s: str) -> bool:
        class State(Enum):
            STATE_INITIAL = auto()
            STATE_INT_SIGN = auto()
            STATE_INTEGER = auto()
            STATE_POINT = auto()
            STATE_POINT_WITHOUT_INT = auto()
            STATE_FRACTION = auto()
            STATE_EXP = auto()
            STATE_EXP_SIGN = auto()
            STATE_EXP_NUMBER = auto()
            STATE_END = auto()

        class CharType(Enum):
            CHAR_NUMBER = auto()
            CHAR_EXP = auto()
            CHAR_POINT = auto()
            CHAR_SIGN = auto()
            CHAR_SPACE = auto()
            CHAR_ILLEGAL = auto()

        def toCharType(ch):
            if ch.isdigit():
                return CharType.CHAR_NUMBER
            elif ch.lower() == 'e':
                return CharType.CHAR_EXP
            elif ch == '.':
                return CharType.CHAR_POINT
            elif ch == '+' or ch == '-':
                return CharType.CHAR_SIGN
            elif ch == ' ':
                return CharType.CHAR_SPACE
            else:
                return CharType.CHAR_ILLEGAL

        transfer = {
            State.STATE_INITIAL: {
                CharType.CHAR_SPACE: State.STATE_INITIAL,
                CharType.CHAR_NUMBER: State.STATE_INTEGER,
                CharType.CHAR_POINT: State.STATE_POINT_WITHOUT_INT,
                CharType.CHAR_SIGN: State.STATE_INT_SIGN,
            },
            State.STATE_INT_SIGN: {
                CharType.CHAR_NUMBER: State.STATE_INTEGER,
                CharType.CHAR_POINT: State.STATE_POINT_WITHOUT_INT,
            },
            State.STATE_INTEGER: {
                CharType.CHAR_NUMBER: State.STATE_INTEGER,
                CharType.CHAR_EXP: State.STATE_EXP,
                CharType.CHAR_POINT: State.STATE_POINT,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_POINT: {
                CharType.CHAR_NUMBER: State.STATE_FRACTION,
                CharType.CHAR_EXP: State.STATE_EXP,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_POINT_WITHOUT_INT: {
                CharType.CHAR_NUMBER: State.STATE_FRACTION,
            },
            State.STATE_FRACTION: {
                CharType.CHAR_NUMBER: State.STATE_FRACTION,
                CharType.CHAR_EXP: State.STATE_EXP,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_EXP: {
                CharType.CHAR_NUMBER: State.STATE_EXP_NUMBER,
                CharType.CHAR_SIGN: State.STATE_EXP_SIGN,
            },
            State.STATE_EXP_SIGN: {
                CharType.CHAR_NUMBER: State.STATE_EXP_NUMBER,
            },
            State.STATE_EXP_NUMBER: {
                CharType.CHAR_NUMBER: State.STATE_EXP_NUMBER,
                CharType.CHAR_SPACE: State.STATE_END,
            },
            State.STATE_END: {
                CharType.CHAR_SPACE: State.STATE_END,
            },
        }

        st = State.STATE_INITIAL
        for ch in s:
            char_type = toCharType(ch)
            if char_type not in transfer[st]:
                return False
            st = transfer[st][char_type]

        return st in [State.STATE_INTEGER, State.STATE_POINT, State.STATE_FRACTION, 
                      State.STATE_EXP_NUMBER, State.STATE_END]

Explore

有限状态自动机(FSM)是处理字符串验证问题的合适选择,因为它能够明确地表示和管理不同的输入字符应该导致的状态转换。FSM通过定义一系列的状态和在接收到特定输入时从一个状态到另一个状态的转移,可以清晰地模拟字符串分析的过程。对于复杂的规则,如有效数字的定义,FSM帮助我们组织和实现这些规则,使得每一个转换都是预定义和受控的。这种方法提高了代码的可维护性和扩展性,同时也便于调试和验证。

在构建状态转移图时,首先需要对问题的规则和要求有深刻理解,然后根据这些规则定义状态和可能的字符类型。通过分析每种字符在各个状态下的合法转移,构建状态转移表。为了确保包括所有必要的状态和转移,通常需要仔细考虑所有边界情况和特殊情况。尽管通过细致的规划和测试可以最小化遗漏,但在复杂的系统中完全避免遗漏是具有挑战性的,可能需要多次迭代和测试来发现并修正漏洞。

`STATE_POINT_WITHOUT_INT`状态表示的是小数点前没有任何数字的情况,比如输入'.123',这种情况下,小数点是输入字符串的首个有效字符。而`STATE_POINT`状态则用于处理小数点后有整数部分的情况,即小数点前已经有数字了,如'123.'。这两个状态的区别在于对小数点前是否存在数字的处理,这反映了有效数字定义中小数的不同表现形式。

在状态机的设计中,有一个专门的状态`STATE_END`用于处理字符串末尾的空格。如果输入字符串在经历了有效的数字状态(如整数、小数或指数部分)后遇到空格,状态会转移到`STATE_END`。在`STATE_END`状态下,任何额外的空格字符都会保持在`STATE_END`状态,确保字符串的结尾空格不会影响数字的有效性。最终,如果字符串在完成解析后处于`STATE_END`或其他接受状态,字符串被认为是有效的数字。