题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
- 若干空格
- 一个 小数 或者 整数
- (可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数
- 若干空格
小数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(‘+’ 或 ‘-’)
- 下述格式之一:
- 至少一位数字,后面跟着一个点 ‘.’
- 至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
- 一个点 ‘.’ ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(‘+’ 或 ‘-’)
- 至少一位数字
部分数值列举如下:
[“+100”, “5e2”, “-123”, “3.1416”, “-1E-16”, “0123”]
部分非数值列举如下:
[“12e”, “1a3.14”, “1.2.3”, “±5”, “12e+5.4”]
题目解析
看到这道题我没有头绪,感觉很难理清楚逻辑。我知道遇到字符的时候,需要做判断。做完判断后,可能是非数值字符串,直接返回结果;如果不能判断,再继续读取下一个字符。这种朴素的想法,没想到有人给这种判断加了一个耳目一新的名字:状态。判断的过程叫做状态转移。
定义一个状态——字符串当前的状态;状态转移的条件——读进了一个字符。
法则1:(如何列举可能的状态)
将问题的基本域列举出来。
对于本题,我们写一个最复杂的数值字符串,+213.67E-06,前后有空格,从左往右,依次可想有空格、符号位、数值域、小数点、E/e这5种基本的状态。但是这5种状态并不能完整地概括所有的状态转移。例如,考察数值域的状态,进入数值域是读入一个数字即可,但是数值域的转移却有可能发生变化。小数点前的数值后读入一个小数点是合法的,小数点后的数值读入一个小数点是非法的。读入小数点之后会出现两种不同的状态。
法则2:(如何正确地定义状态)
每一个状态的转移,不能受到其他状态的影响,只能根据输入来判断。如果收到其他状态的影响,则说明定义的状态独立性不够,需要拆分。
以小数点为例,如果读入一个小数点,会进入一个名为小数点的状态。在分析这个状态如何往外转移时,如果读入一个数字,是否能够进入数字域的状态?这个是值得商榷的。因为小数中,小数点后面是可以不接数字的,只要小数点前面有数字;同时,如果小数点前面没有数字,则小数点后必须有数字。如果小数点后读入一个’E/e’,就会无法判断如何转移了。因为,数后小数点是可以接’E/e’的;但是非数后小数点却不能直接接’E/e’,必须接数字,直接跟’E/e’是非法。
根据前面两个法则,可以划分成如下的状态转移图:
这种划分下有11个状态。注意到Start状态和串首空格态的输入和输出其实是一样的,故可以简化成10个状态。
代码1:使用map来存储输入字符 -> 转移状态。这个代码得搭配上图才能比较好的理解,状态1,…,10使用数字来表示的,无法表示直观的含义。
class Solution {
public:
bool isNumber(string s) {
vector<unordered_map<char, int>> statesMap;
statesMap.emplace_back(unordered_map<char, int>{{'b',1}, {'s', 2}, {'d', 3}, {'c', 5}}); // state 0: start
statesMap.emplace_back(unordered_map<char,int>{{'b', 1}, {'s',2}, {'d', 3},{'c',5}}); // state 1: 串首空格
statesMap.emplace_back(unordered_map<char,int>{{'d', 3},{'c',5}}); // state 2: E前符号位
statesMap.emplace_back(unordered_map<char,int>{{'d', 3}, {'c', 4}, {'e', 7}, {'b', 10}}); // state 3: 点前数字域
statesMap.emplace_back(unordered_map<char,int>{{'d',6},{'b',10},{'e',7}}); // state 4: 数后小数点
statesMap.emplace_back(unordered_map<char,int>{{'d',6}}); // state 5: 非数后小数点
statesMap.emplace_back(unordered_map<char,int>{{'d', 6},{'b', 10},{'e', 7}}); // state 6: 点后数字域
statesMap.emplace_back(unordered_map<char,int>{{'s', 8},{'d',9}}); // state 7: E/e
statesMap.emplace_back(unordered_map<char,int>{{'d',9}}); // state 8: E后符号位
statesMap.emplace_back(unordered_map<char,int>{{'d', 9}, {'b', 10}}); // state 9: E后数字域
statesMap.emplace_back(unordered_map<char,int>{{'b',10}}); // state 10: 串后字符串
int state = 0;
for (const auto &c : s) {
char flag = 'x';
if (c == ' ') {
flag = 'b';
}else if (c == '+' || c == '-') {
flag = 's';
}else if ( c == 'E' || c == 'e') {
flag = 'e';
}else if (c == '.') {
flag = 'c';
}else if (c >= '0' && c <= '9') {
flag = 'd';
}
if (statesMap[state].find(flag) == statesMap[state].end()) {
return false;
}else {
state = statesMap[state][flag];
}
}
return state != 1 && state != 2 && state != 5 && state != 7 && state != 8;
}
};
代码2:使用枚举类。
class Solution {
public:
enum State {
STATE_INITIAL,
STATE_INT_SIGN,
STATE_INTEGER,
STATE_POINT,
STATE_POINT_WITHOUT_INT,
STATE_FRACTION,
STATE_EXP,
STATE_EXP_SIGN,
STATE_EXP_NUMBER,
STATE_END
};
enum CharType {
CHAR_NUMBER,
CHAR_EXP,
CHAR_POINT,
CHAR_SIGN,
CHAR_SPACE,
CHAR_ILLEGAL
};
CharType toCharType(char ch) {
if (ch >= '0' && ch <= '9') {
return CHAR_NUMBER;
} else if (ch == 'e' || ch == 'E') {
return CHAR_EXP;
} else if (ch == '.') {
return CHAR_POINT;
} else if (ch == '+' || ch == '-') {
return CHAR_SIGN;
} else if (ch == ' ') {
return CHAR_SPACE;
} else {
return CHAR_ILLEGAL;
}
}
bool isNumber(string s) {
unordered_map<State, unordered_map<CharType, State>> transfer{
{
STATE_INITIAL, {
{CHAR_SPACE, STATE_INITIAL},
{CHAR_NUMBER, STATE_INTEGER},
{CHAR_POINT, STATE_POINT_WITHOUT_INT},
{CHAR_SIGN, STATE_INT_SIGN}
}
}, {
STATE_INT_SIGN, {
{CHAR_NUMBER, STATE_INTEGER},
{CHAR_POINT, STATE_POINT_WITHOUT_INT}
}
}, {
STATE_INTEGER, {
{CHAR_NUMBER, STATE_INTEGER},
{CHAR_EXP, STATE_EXP},
{CHAR_POINT, STATE_POINT},
{CHAR_SPACE, STATE_END}
}
}, {
STATE_POINT, {
{CHAR_NUMBER, STATE_FRACTION},
{CHAR_EXP, STATE_EXP},
{CHAR_SPACE, STATE_END}
}
}, {
STATE_POINT_WITHOUT_INT, {
{CHAR_NUMBER, STATE_FRACTION}
}
}, {
STATE_FRACTION,
{
{CHAR_NUMBER, STATE_FRACTION},
{CHAR_EXP, STATE_EXP},
{CHAR_SPACE, STATE_END}
}
}, {
STATE_EXP,
{
{CHAR_NUMBER, STATE_EXP_NUMBER},
{CHAR_SIGN, STATE_EXP_SIGN}
}
}, {
STATE_EXP_SIGN, {
{CHAR_NUMBER, STATE_EXP_NUMBER}
}
}, {
STATE_EXP_NUMBER, {
{CHAR_NUMBER, STATE_EXP_NUMBER},
{CHAR_SPACE, STATE_END}
}
}, {
STATE_END, {
{CHAR_SPACE, STATE_END}
}
}
};
int len = s.length();
State st = STATE_INITIAL;
for (int i = 0; i < len; i++) {
CharType typ = toCharType(s[i]);
if (transfer[st].find(typ) == transfer[st].end()) {
return false;
} else {
st = transfer[st][typ];
}
}
return st == STATE_INTEGER || st == STATE_POINT || st == STATE_FRACTION || st == STATE_EXP_NUMBER || st == STATE_END;
}
};
引伸基础问题:
- 什么是枚举类?
- C++中的关联容器基本操作:1) 如何判断是否含有key? 2)如何通过Key查找value? 2)如何添加key-value?