1. 题目来源
链接:表示数值的字符串
来源:LeetCode——《剑指-Offer》专项
2. 题目说明
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 "+100"、"5e2"、"-123"、"3.1416"、"0123"及"-1E-16"
都表示数值,但 "12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"
都不是。
3. 题目解析
方法一:各种情况分类分析+优化
有一说一,这题在 LeetCode
出的是真的恶心人…
以下解法参考了:参考了网上另一篇博文 (http://yucoding.blogspot.com/2013/05/leetcode-question-118-valid-number.html),处理了各种情况。
首先,从题目中给的一些例子可以分析出来,所需要关注的除了数字以外的特殊字符有空格 ‘ ’
, 小数点 '.'
, 自然数 'e/E'
, 还要加上正负号 '+/-'
, 除了这些字符需要考虑意外,出现了任何其他的字符,可以马上判定不是数字。下面来一 一分析这些出现了也可能是数字的特殊字符:
-
空格
‘ ’
: 空格分为两种情况需要考虑,一种是出现在开头和末尾的空格,另一种是出现在中间的字符。出现在开头和末尾的空格不影响数字,而一旦中间出现了空格,则立马不是数字。解决方法:预处理时去掉字符的首位空格,中间再检测到空格,则判定不是数字。 -
小数点
'.'
:小数点需要分的情况较多,首先的是小数点只能出现一次,但是小数点可以出现在任何位置,开头".3"
,中间"1.e2"
, 以及结尾"1."
, 而且需要注意的是,小数点不能出现在自然数'e/E'
之后,如"1e.1" false
,"1e1.1" false
。还有,当小数点位于末尾时,前面必须是数字,如"1." true
," -." false
。解决方法:开头中间结尾三个位置分开讨论情况。 -
自然数
'e/E'
:自然数的前后必须有数字,即自然数不能出现在开头和结尾,如"e" false
,".e1" false
,"3.e" false
,"3.e1" true
。而且小数点只能出现在自然数之前,还有就是自然数前面不能是符号,如"+e1" false
,"1+e" false
。 解决方法:开头中间结尾三个位置分开讨论情况。 -
正负号
'+/-"
,正负号可以再开头出现,可以再自然数e
之后出现,但不能是最后一个字符,后面得有数字,如"+1.e+5" true
。解决方法:开头中间结尾三个位置分开讨论情况。
下面我们开始正式分开头中间结尾三个位置来讨论情况:
-
在讨论三个位置之前做预处理,去掉字符串首尾的空格,可以采用两个指针分别指向开头和结尾,遇到空格则跳过,分别指向开头结尾非空格的字符
-
对首字符处理,首字符只能为数字或者正负号
'+/-'
,我们需要定义三个flag
在标示我们是否之前检测到过小数点,自然数和正负号。首字符如为数字或正负号,则标记对应的flag
,若不是,直接返回false
-
对中间字符的处理,中间字符会出现五种情况,数字,小数点,自然数,正负号和其他字符
-
若是数字,标记
flag
并通过 -
若是自然数,则必须是第一次出现自然数,并且前一个字符不能是正负号,而且之前一定要出现过数字,才能标记
flag
通过 -
若是正负号,则之前的字符必须是自然数
e
,才能标记flag
通过 -
若是小数点,则必须是第一次出现小数点并且自然数没有出现过,才能标记
flag
通过 -
若是其他,返回
false
- 对尾字符处理,最后一个字符只能是数字或小数点,其他字符都返回
false
-
若是数字,返回
true
-
若是小数点,则必须是第一次出现小数点并且自然数没有出现过,还有前面必须是数字,才能返回
true
至此,if-else
可以解决这个问题了。
再分析优化:
上面的写法略为复杂,我们尝试着来优化一下,根据上面的分析,所有的字符可以分为六大类:空格,符号,数字,小数点,自然底数和其他字符,我们需要五个标志变量,num, dot, exp, sign
分别表示数字,小数点,自然底数和符号是否出现,numAfterE
表示自然底数后面是否有数字,那么我们分别来看各种情况:
-
空格: 我们需要排除的情况是,当前位置是空格而后面一位不为空格,但是之前有数字,小数点,自然底数或者符号出现时返回
false
-
符号:符号前面如果有字符的话必须是空格或者是自然底数,标记
sign
为true
-
数字:标记
num
和numAfterE
为true
-
小数点:如果之前出现过小数点或者自然底数,返回
false
,否则标记dot
为true
-
自然底数:如果之前出现过自然底数或者之前从未出现过数字,返回
false
,否则标记exp
为true
,numAfterE
为false
-
其他字符:返回
false
最后返回 num && numAfterE
即可。
参见代码如下:
// 执行用时 :8 ms, 在所有 C++ 提交中击败了50.94%的用户
// 内存消耗 :8.4 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
bool isNumber(string s) {
bool num = false, numAfterE = true, dot = false, exp = false, sign = false;
int n = s.size();
for (int i = 0; i < n; ++i) {
if (s[i] == ' ') {
if (i < n - 1 && s[i + 1] != ' ' && (num || dot || exp || sign)) return false;
} else if (s[i] == '+' || s[i] == '-') {
if (i > 0 && s[i - 1] != 'e' && s[i - 1] != ' ') return false;
sign = true;
} else if (s[i] >= '0' && s[i] <= '9') {
num = true;
numAfterE = true;
} else if (s[i] == '.') {
if (dot || exp) return false;
dot = true;
} else if (s[i] == 'e') {
if (exp || !num) return false;
exp = true;
numAfterE = false;
} else return false;
}
return num && numAfterE;
}
};
方法二:普通思考方式
最开始的思考,我就郁闷 "-1E-16"
在题目说明里就为数值了,然后跑个代码控制台给返回个 false
,我惊了…
这个还是比较好理解的,代码关键处也是进行了详细注释了。
参见代码如下:
// 执行用时 :12 ms, 在所有 C++ 提交中击败了15.09%的用户
// 内存消耗 :8.5 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
bool isNumber(string s) {
// 移除首尾空格
int start = 0, end = s.size() - 1;
while (start < s.size() && s[start] == ' ') start++;
while (end >= 0 && s[end] == ' ') end--;
s = s.substr(start, end - start + 1);
int e = 0, dot = 0; // 统计e 和 . 的出现次数
for (char c : s) {
if (c == 'e') e++;
else if (c == '.') dot++;
else if (c == '+' || c == '-');
else if (c >= '0' && c <= '9');
else return false; // 除了+-e.0123456789以外都非法 包括空白
}
if (e > 1 || dot > 1) return false; // e 和 . 只能最多出現一次
e = s.find("e");
if (e != -1) {
string exp = s.substr(e + 1); // 分离出指数部分
if (exp.find(".") != -1) return false; // 指数有個不同規則是只能是整數
if (!commonCheck(exp)) return false;
s = s.substr(0, e);
}
if (!commonCheck(s)) return false;
return true;
}
bool commonCheck(string s) {
if (!s.empty() && (s[0] == '+' || s[0] == '-')) s.erase(s.begin()); // 如果首字串是+- 合法, 先移除掉
if (s.empty()) return false;
if (s.find("+") != -1) return false;
if (s.find("-") != -1) return false;
if (s == ".") return false; // .可以出現在任何位位置, 但唯独不可以只有自己
return true;
}
};
方法三:题解大佬极其清晰的解法(查看必有收获)
来源:algsCG:C++:模拟题(思路简单,逻辑清晰) LeetCode
该题题解里,大佬的写法,思路很清楚,代码注释完整,放这镇一镇这题!
处理空格都这么优雅,爱了~
参见代码如下:
// 执行用时 :0 ms, 在所有 C++ 提交中击败了100.00%的用户
// 内存消耗 :8.5 MB, 在所有 C++ 提交中击败了100.00%的用户
class Solution {
public:
bool isNumber(string s) {
//1、从首尾寻找s中不为空格首尾位置,也就是去除首尾空格
int i=s.find_first_not_of(' ');
if(i==string::npos)return false;
int j=s.find_last_not_of(' ');
s=s.substr(i,j-i+1);
if(s.empty())return false;
//2、根据e来划分底数和指数
int e=s.find('e');
//3、指数为空,判断底数
if(e==string::npos)return judgeP(s);
//4、指数不为空,判断底数和指数
else return judgeP(s.substr(0,e))&&judgeS(s.substr(e+1));
}
bool judgeP(string s)//判断底数是否合法
{
bool result=false,point=false;
int n=s.size();
for(int i=0;i<n;++i)
{
if(s[i]=='+'||s[i]=='-'){//符号位不在第一位,返回false
if(i!=0)return false;
}
else if(s[i]=='.'){
if(point)return false;//有多个小数点,返回false
point=true;
}
else if(s[i]<'0'||s[i]>'9'){//非纯数字,返回false
return false;
}
else{
result=true;
}
}
return result;
}
bool judgeS(string s)//判断指数是否合法
{
bool result=false;
//注意指数不能出现小数点,所以出现除符号位的非纯数字表示指数不合法
for(int i=0;i<s.size();++i)
{
if(s[i]=='+'||s[i]=='-'){//符号位不在第一位,返回false
if(i!=0)return false;
}
else if(s[i]<'0'||s[i]>'9'){//非纯数字,返回false
return false;
}
else{
result=true;
}
}
return result;
}
};