力扣每日一题:65. 有效数字

题目:65. 有效数字

难度: 困难

题目

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

  • 一个 小数 或者 整数
  • (可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数

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

  • (可选)一个符号字符(’+’ 或 ‘-’)
  • 下述格式之一:
    • 至少一位数字,后面跟着一个点 ‘.’
    • 至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
    • 一个点 ‘.’ ,后面跟着至少一位数字

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

  • (可选)一个符号字符(’+’ 或 ‘-’)
  • 至少一位数字

部分有效数字列举如下:

  • [“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

示例4

输入:s = “.1”
输出:true

提示

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

来源:力扣(LeetCode)
链接https://leetcode-cn.com/problems/valid-number/
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。



解题思路

(1)模拟法

最容易想到的方法就是模拟法,根据「有效数字定义」梳理各部分的限制。

定义几个标志标识:isE:是否有’E’或’e’,isDot:是否有点,isSign:是否有符号,isNum:是否有数字。

遍历字符串s,根据设计规则有以下情况:

  • 遇到’E’或’e’:(1)已经有E了,错误(2)E之前需要有数字
  • 遇到’+‘或’-’:(1)已有符号,错误(2)符号之前须有数字(3)符号之前不可有点
  • 遇到’.’://(1)已有点,错误(2)科学计算法之后需要是整数,所以不可有点(小数)
  • 遇到数字:isNum = true
  • 其它情况,return false

考虑到科学记数法的特殊性,所以在遇到’E’和’e’时,需要将isEisDotisSign三个标志位清除,因为在之后的判断中又是重新的一轮判断,不可被之前所影响。

class Solution {
public:
    bool isNumber(string s) {

        int n = s.size();
        int i = 0;
        bool isE = false, isDot = false, isSign = false, isNum = false;

        while (i < n)
        {
            if (s[i] == 'e' || s[i] == 'E')
            {
                //(1)已经有E了,错误(2)E之前需要有数字
                if (isE || !isNum) return false;
                //有了E之后,点、符号、数字等标识符清空,因为之后又是一个新的是否有效数字判断,该步十分关键
                isE = true; isDot = false; isSign = false; isNum = false;
            }
            else if (s[i] == '+' || s[i] == '-')
            {
                //(1)已有符号,错误(2)符号之前须有数字(3)符号之前不可有点
                if (isSign || isNum || isDot) return false;
                isSign = true;
            }
            else if (s[i] == '.')
            {
                //(1)已有点,错误(2)科学计算法之后需要是整数,所以不可有点(小数)
                if (isDot || isE) return false;
                isDot = true;
            }
            else if (s[i] >= '0' && s[i] <= '9')
            {
                isNum = true;
            }
            else
            {
                return false;
            }

            i++;
        }
        return isNum;
    }
};

(2)状态机

确定有限状态自动机,有一系列状态:「初始状态」、「接受状态」。在算法领域,它与大名鼎鼎的字符串查找算法KMP 算法有着密切的关联;在工程领域,它是实现正则表达式的基础。

其运行的流程如下:

  • 起初,这个自动机处于「初始状态」。
  • 随后,它顺序地读取字符串中的每一个字符,并根据当前状态和读入的字符,按照某个事先约定好的「转移规则」,从当前状态转移到下一个状态;
  • 当状态转移完成后,它就读取下一个字符。
  • 当字符串全部读取完毕后,如果自动机处于某个「接受状态」,则判定该字符串「被接受」;否则,判定该字符串「被拒绝」。

对于本题,因为不同的字符将导致判断进入不同的状态,所以使用状态机求解是个不错的选择。用「当前处理到字符串的哪个部分」当作状态的表述。根据这一技巧,不难挖掘出所有状态:

  1. 初始状态
  2. 符号位
  3. 整数部分
  4. 左侧有整数的小数点
  5. 左侧无整数的小数点(根据前面的第二条额外规则,需要对左侧有无整数的两种小数点做区分)
  6. 小数部分
  7. 字符 e
  8. 指数部分的符号位
  9. 指数部分的整数部分

根据合法字符串的格式,状态机的转换如下:
在这里插入图片描述
同时,转移失败即下一字符找不到「接受状态」,则判断该字符串「不接受」,匹配结束。

class Solution {
public:
    enum State 
    {
        STATE_INITIAL,
        STATE_SIGN,
        STATE_INTEGER,
        STATE_DOT_DECIMALS,
        STATE_DOT_INTEGER,
        STATE_DOT,
        STATE_EXP,
        STATE_EXP_SIGN,
        STATE_EXP_NUM,
        STATE_END
    };

    enum CharType
    {
        CHAR_NUM,
        CHAR_EXP,
        CHAR_DOT,
        CHAR_SIGN,
        CHAR_ERROR
    };
    CharType getType(char ch)
    {
        if (ch >= '0' && ch <= '9')
        {
            return CHAR_NUM;
        }
        else if (ch == 'E' || ch == 'e')
        {
            return CHAR_EXP;
        }
        else if (ch == '.')
        {
            return CHAR_DOT;
        }
        else if (ch == '+' || ch == '-')
        {
            return CHAR_SIGN;
        }
        else
        {
            return CHAR_ERROR;
        }
    }
    bool isNumber(string s) {

        unordered_map<State, unordered_map<CharType, State>> mapState= {

            {
                STATE_INITIAL, {
                    {CHAR_NUM, STATE_INTEGER},
                    {CHAR_DOT, STATE_DOT_DECIMALS},
                    {CHAR_SIGN, STATE_SIGN}
                }
            },

            {
                STATE_SIGN, {
                    {CHAR_NUM, STATE_INTEGER},
                    {CHAR_DOT, STATE_DOT_DECIMALS}
                }
            },

            {
                STATE_INTEGER, {
                    {CHAR_NUM, STATE_INTEGER},
                    {CHAR_DOT, STATE_DOT_INTEGER},
                    {CHAR_EXP, STATE_EXP}
                }
            },

            {
                STATE_DOT_DECIMALS, {
                    {CHAR_NUM, STATE_DOT}
                }
            },

            {
                STATE_DOT_INTEGER, {
                    {CHAR_NUM, STATE_DOT},
                    {CHAR_EXP, STATE_EXP}
                }
            },

            {
                STATE_DOT, {
                    {CHAR_NUM, STATE_DOT},
                    {CHAR_EXP, STATE_EXP}
                }
            },

            {
                STATE_EXP, {
                    {CHAR_SIGN, STATE_EXP_SIGN},
                    {CHAR_NUM, STATE_EXP_NUM}
                }
            },

            {
                STATE_EXP_SIGN, {
                    {CHAR_NUM, STATE_EXP_NUM}
                }
            },

            {
                STATE_EXP_NUM, {
                    {CHAR_NUM, STATE_EXP_NUM}
                }
            }
        };


        //开始
        int n = s.size();
        State st = STATE_INITIAL;

        for (int i = 0; i < n; i++)
        {
            CharType ct = getType(s[i]);
            if (mapState[st].find(ct) == mapState[st].end())
            {
                return false;
            }
            else
            {
                st = mapState[st][ct];//修改状态
            }
        }

        //整数、小数、科学记数法整数、左边为整数小数点
        return st == STATE_INTEGER || st == STATE_DOT || st == STATE_EXP_NUM || st == STATE_DOT_INTEGER;
        
    }
};

(3)正则表达式

注意,正则表达式pattern使用类的静态变量或者全局变量,避免重复构造的开销。

class Solution {
public:
    static const regex pattern;

    bool isNumber(string s) {

        return regex_match(s, pattern);
    }
};
const regex Solution::pattern("[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[Ee][+-]?\\d+)?");

const regex pattern = regex("[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[Ee][+-]?\\d+)?");
class Solution {
public:
    bool isNumber(string s) {

        return regex_match(s, pattern);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗夜无风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值