剑指 Offer 20. 表示数值的字符串
题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
-
若干空格
-
一个 小数 或者 整数
-
(可选)一个 'e' 或 'E' ,后面跟着一个 整数
-
若干空格
小数(按顺序)可以分成以下几个部分:
-
(可选)一个符号字符('+' 或 '-')
-
下述格式之一:
-
至少一位数字,后面跟着一个点 '.'
-
至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
-
一个点 '.' ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
-
(可选)一个符号字符('+' 或 '-')
-
至少一位数字
部分数值列举如下:
-
["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]
部分非数值列举如下:
-
["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]
示例 1:
输入:s = "0" 输出:true
示例 2:
输入:s = "e" 输出:false
示例 3:
输入:s = "." 输出:false
示例 4:
输入:s = " .1 " 输出:true
提示:
-
1 <= s.length <= 20
-
s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。
作者:Krahets 链接:力扣 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解题思路
本题使用有限状态自动机。根据字符类型和合法数值的特点,先定义状态,最后编写代码即可。
代码用一个 while 循环扫描整个字符串,只处理一次每个字符,并且避免了很多复杂的判断。
-
在跳过前导空格和正负号后,每次处理当前字符时,根据不同情况更新若干 bool 变量,表示是否已经看到数字、小数点、科学计数法符号以及它们之间的各种关系。
-
如果当前字符不是数字、小数点、科学计数法符号、正负号或空格,则说明字符串不合法,直接返回 false。
-
处理完字符串后,再跳过末尾空格,检查数字和科学计数法符号出现的正确性以及结尾是否为数字即可。
字符类型:
空格 「 」、数字「 0—9 」 、正负号 「 +, − 」 、小数点 「 . 」 、幂符号 「 e, E 」
状态定义
-
seen_num = false 数字是否存在
-
seen_dot = false 小数点是否存在
-
seen_e = false 指数e/E是否存在
-
seen_digit_after_e = true 指数e/E后面的数字是否存在
最后根据各种可能情况返回 true 或 false
第一步:
先跳过字符串开头的空格
int i = 0;
while (i < len && s[i] == ' ') i++;
第二步:
如果遇到+、-号则跳过
if (i < len && (s[i] == '+' || s[i] == '-')) i++;
第四步:
开始遍历字符串
如果是数字,则将数字状态seen_num变为true。
并且指数后面的数字状态seen_digit_after_e也变为true。
如果是小数点,则将小数点状态seen_num变为true。
如果之前出现过小数点或者指数,则不合法,需要return false。
例如:
3.14.15(不合法,出现>1次小数点)
-3.14e+1.5(不合法,指数e后面出现>0次小数点)
//如果是数字
if (c>='0'&&c<='9') {
seen_num = true;
seen_digit_after_e = true;
}
// 如果是小数点
else if (c == '.') {
if (seen_dot || seen_e) return false;
seen_dot = true;
}
第五步:
当遇到指数e/E的时候,有两种不合法情况:
- 指数前面没有出现数字 例如:e+5、+e5+5
- 指数前面已经出现过指数 例如:-3.14e+5e+5
此时则需要return false。
假如没有出现不合况,则将seen_e变为true。
指数后面的数字状态seen_digit_after_e重置为false(用来监测指数后面是否出现没有数字(不合法)情况 例如:-3.14e)
// 如果是指数符号
else if (c == 'e' || c == 'E') {
if (!seen_num || seen_e) return false;
seen_e = true;
seen_digit_after_e = false;
}
第六步:
如果在遍历字符串的过程中又遇到了+、-号(因为在遍历之前已经跳过一次+、-),此次需要检查+、-号是否出现在指数后面,否则会出现以下不合法情况:
- 在指数前出现两次+、-号 例如:-3.14+e+5
- 在除了指数后面一位的位置出现了+、-号 例如:-3.14e5+、-3.14e+5-5
合法情况为在跳过了开头+、-号的情况下,只能在指数e/E后面一位出现+、-号,如果合法则跳过。
// 如果是正负号
else if (c == '+' || c == '-') {
if (s[i-1] != 'e' && s[i-1] != 'E') return false;
}
第七步:
一直遍历下去,如果遇到其他字符,则为非法,return false
直到再次遇到空格则跳出遍历,开始进行末尾空格状态检查
// 如果是空格
else if (c == ' ') {
break; // 跳出循环
}
// 其他情况都是非法字符
else {
return false; // 返回 false
}
i++; // 指针向右移动一位
第八步:
检查结尾空格,如果检查过程中又遇到了非空格字符,则视为不合法,return false
例如:-3.14e+5 67 此处5后面开始检查空格,检查过程中遇到了6则停止检查,不合法
while (i < len && s[i] == ' ') i++;
第九步:
最后判断是否满足数值形式:
- seen_num==true 字符串中必须有数字
- seen_digit_after_e==true 指数后面必须有数字
- i == len; 成功遍历到末尾(因为空格检查的时候使用了i++,所以此处不是i==len-1)
全部判断成功后则返回true,否则返回false
若顺利检查到字符串末尾,未出现不合法情况则结束检查
return seen_num && seen_digit_after_e && i == len;
本人通过代码(附注释)
#include<iostream>
#include<string>
using namespace std;
class Solution {
public:
bool isNumber(string s) {
// 获取字符串长度
int len = s.length();
// 初始化标志位
bool seen_num = false, seen_dot = false, seen_e = false, seen_digit_after_e = true;
// 初始化指针
int i = 0;
// 跳过字符串开头的空格
while (i < len && s[i] == ' ') i++;
// 如果是正负号,跳过
if (i < len && (s[i] == '+' || s[i] == '-')) i++;
// 遍历字符串
while (i < len) {
char c = s[i]; // 获取当前字符
// 如果是数字
if (c>='0'&&c<='9') {
seen_num = true; // 设置数字标志位为 true
seen_digit_after_e = true; // 若之前出现过 e,将指数后面的数字标志位也设置为 true
}
// 如果是小数点
else if (c == '.') {
if (seen_dot || seen_e) return false; // 如果已经出现过小数点或者指数,返回 false
seen_dot = true; // 设置小数点标志位为 true
}
// 如果是指数符号
else if (c == 'e' || c == 'E') {
if (!seen_num || seen_e) return false; // 如果之前没有出现过数字或者已经出现过指数,返回 false
seen_e = true; // 设置指数标志位为 true
seen_digit_after_e = false; // 将指数后面的数字标志位设置为 false
}
// 如果是正负号
else if (c == '+' || c == '-') {
if (s[i-1] != 'e' && s[i-1] != 'E') return false; // 如果前一个字符不是 e 或 E,返回 false
}
// 如果是空格
else if (c == ' ') {
break; // 跳出循环
}
// 其他情况都是非法字符
else {
return false; // 返回 false
}
i++; // 指针向右移动一位
}
// 跳过结尾的空格(如果跳过空格的过程中又遇到了不是空格的字符则终止遍历,视为false)
while (i < len && s[i] == ' ') i++;
// 判断是否满足数值形式(必须存在数字,并且指数e后面也必须有数字,并且能够遍历字符串到结尾
return seen_num && seen_digit_after_e && i == len;
}
};
int main(){
Solution sol;
string s;
getline(cin,s); // 获取用户输入的字符串(支持字符串中有空格输入)
if(sol.isNumber(s)) cout<<"true"; // 如果是数值形式,输出 true
else cout<<"false"; // 否则输出 false
return 0;
}
这个代码的时间复杂度为 O(n),空间复杂度为 O(1)。