题目: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’时,需要将isE
、 isDot
、isSign
三个标志位清除,因为在之后的判断中又是重新的一轮判断,不可被之前所影响。
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 算法有着密切的关联;在工程领域,它是实现正则表达式的基础。
其运行的流程如下:
- 起初,这个自动机处于「初始状态」。
- 随后,它顺序地读取字符串中的每一个字符,并根据当前状态和读入的字符,按照某个事先约定好的「转移规则」,从当前状态转移到下一个状态;
- 当状态转移完成后,它就读取下一个字符。
- 当字符串全部读取完毕后,如果自动机处于某个「接受状态」,则判定该字符串「被接受」;否则,判定该字符串「被拒绝」。
对于本题,因为不同的字符将导致判断进入不同的状态,所以使用状态机求解是个不错的选择。用「当前处理到字符串的哪个部分」当作状态的表述。根据这一技巧,不难挖掘出所有状态:
- 初始状态
- 符号位
- 整数部分
- 左侧有整数的小数点
- 左侧无整数的小数点(根据前面的第二条额外规则,需要对左侧有无整数的两种小数点做区分)
- 小数部分
- 字符 e
- 指数部分的符号位
- 指数部分的整数部分
根据合法字符串的格式,状态机的转换如下:
同时,转移失败即下一字符找不到「接受状态」,则判断该字符串「不接受」,匹配结束。
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);
}
};