剑指 Offer(第2版)面试题 20:表示数值的字符串
剑指 Offer(第2版)面试题 20:表示数值的字符串
题目来源:31. 表示数值的字符串
解法1:模拟
注意以下几点:
- 小数可以没有整数部分,例如.123等于0.123;
- 小数点后面可以没有数字,例如233.等于233.0;
- 小数点前面和后面可以有数字,例如233.666;
- 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
- 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4。
可能存在字符串前后有空格的情况,下面的函数可以删除空格:
string delSpaceStr(string s)
{
int n = s.length(), i = 0, j = n - 1;
while (i < n && s[i] == ' ')
i++;
while (j >= 0 && s[j] == ' ')
j--;
if (i <= j)
return s.substr(i, j - i + 1);
return "";
}
代码:
class Solution
{
public:
bool isNumber(string s)
{
if (s[0] == '-' || s[0] == '+')
s = s.substr(1);
if (s.empty() || s[0] == '.' && s.size() == 1)
return false;
int n = s.length();
int point = 0, e = 0;
for (int i = 0; i < n; i++)
{
if (isdigit(s[i]))
{
// 如果 s[i] 是数字 '0'~'9',跳过
continue;
}
else if (s[i] == '.') // 如果 s[i] 是小数点
{
point++;
if (e || point > 1)
{
// 小数点不能超过 1 个,且小数点不能出现在 'e'/'E' 之后
return false;
}
}
else if (s[i] == 'e' || s[i] == 'E')
{
e++;
if (e > 1)
{
return false;
}
if (i == 0 || i == n - 1)
{
// 'e'/'E' 不能出现在数值的开头和末尾
return false;
}
if (i == 1 && s[0] == '.')
{
// 不能有 '.e' 或 '.E'
return false;
}
if (s[i + 1] == '+' || s[i + 1] == '-')
{
// 'e'/'E' 后可以接加减号,但是加减号不能是末尾
if (i + 2 == n)
return false;
// 跳过加减号
i++;
}
}
else
{
// 其他字符都是非法的!
return false;
}
}
return true;
}
};
复杂度分析:
时间复杂度:O(n),其中 n 是字符串 s 的长度。
空间复杂度:O(1)。
解法2:分段匹配
官方解法。
表示数值的字符串遵循模式 A[.[B]][E|eC] 或者 .[B][E|eC],其中 A 是数值的整数部分,B 紧跟小数点,是数值的小数部分,C紧跟着 ‘e’/‘E’,是数值的指数部分。
在数值里可能没有数值的整数部分,因此 A 是可有可无的。如果一个数没有整数部分,那么它必须有小数部分。
另外,A 和 C 都可能以 ‘+’/‘-’ 开头,B 一定不能以 ‘+’/‘-’ 开头。
代码:
class Solution
{
public:
bool isNumber(string s)
{
if (s.empty())
return false;
int idx = 0;
// 扫描数值的整数部分
bool numeric = scanInteger(s, idx);
// 如果出现了 '.',则接下来是数值的小数部分
if (s[idx] == '.')
{
idx++;
numeric = scanUnsignedInteger(s, idx) || numeric;
}
// 如果出现了 'e'/'E',则接下来是数值的指数部分
if (s[idx] == 'e' || s[idx] == 'E')
{
idx++;
numeric = scanInteger(s, idx) && numeric;
}
return numeric && idx == s.length();
}
bool scanInteger(const string s, int &index)
{
if (s[index] == '+' || s[index] == '-')
index++;
return scanUnsignedInteger(s, index);
}
bool scanUnsignedInteger(const string s, int &index)
{
int begin = index;
while (index < s.length() && isdigit(s[index]))
index++;
return index > begin;
}
};
复杂度分析:
时间复杂度:O(n),其中 n 是字符串 s 的长度。
空间复杂度:O(1)。
解法 3:DFA
看不懂的炫技解法,和状态机有关。
class Solution {
public:
bool isNumber(string s) {
if(s.empty()) return false;
int n = s.size();
int state = 0;
vector<bool> finals({0, 0, 0, 1, 0, 1, 1, 0, 1}); // 合法的终止状态
vector<vector<int> > transfer({
{0, 1, 6, 2, -1, -1},
{-1, -1, 6, 2, -1, -1},
{-1, -1, 3, -1, -1, -1},
{8, -1, 3, -1, 4, -1},
{-1, 7, 5, -1, -1, -1},
{8, -1, 5, -1, -1, -1},
{8, -1, 6, 3, 4, -1},
{-1, -1, 5, -1, -1, -1},
{8, -1, -1, -1, -1, -1},
});
for(int i = 0; i < n; ++i) {
state = transfer[state][_make(s[i])];
if(state < 0) return false;
}
return finals[state];
}
private:
int _make(const char& c) {
switch(c) {
case ' ': return 0;
case '+': return 1;
case '-': return 1;
case '.': return 3;
case 'e': return 4;
case 'E': return 4;
default: return _number(c);
}
}
int _number(const char& c) {
if(c >= '0' && c <= '9') return 2;
else return 5;
}
};