版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/82501301
项目地址:https://github.com/SpecialYy/Sword-Means-Offer
题目
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串”+100”,”5e2”,”-123”,”3.1416”和”-1E-16”都表示数值。 但是”12e”,”1a3.14”,”1.2.3”,”+-5”和”12e+4.3”都不是。
解析
预备知识
在一般编程语言中,”.2”,”2.”,”+.2”是合法的数值表示,而”2e”,”e”,”e2”是不合法的数值表示。
思路一
首先观察题目给的样例,什么是合法的数值表示。主要有以下几点要求:
- 肯定不含0 - 9, + , -, e, E之外的字符
- +,-合法的位置处于第0位,或者在e的下一个位置出现,其他地方都是不合法
- e,E只能出现一次,且不能出现在字符串的首部和末尾。
- . 只能出现一次,且 . 不能出现在e之后,. 的左右必然有一方是有数字的
根据以上,可以写出如下代码,所有的情况都在注释给出:
public static boolean isNum(char ch) {
return ch >= '0' && ch <= '9';
}
/**
* 常规方法
* @param str
* @return
*/
public static boolean isNumeric1(char[] str) {
if(str == null || str.length == 0) {
return false;
}
boolean decimalPoint = false, e = false;
for(int i = 0; i < str.length; i++) {
char ch = str[i];
if(ch == '+' || ch == '-') {
/**
* +,—出现在末尾,比如"+", "2+", "2e+"
* 在没有e出现的时候,就出现在了非0位置,比如"2+3"
* 在出现e之后,"+, -"没有紧挨着e出现,比如"2.3e4+5"
* 这些都是“+,—”不合法
*/
if(i == str.length - 1 || (i != 0 && !e)
|| (e && str[i - 1] != 'e' && str[i - 1] != 'E')) {
return false;
}
}else if(ch == '.') {
/**
* 当. e 出现过后,. 不能再出现了
* 当. 为第0位时,如果串本身就为1,即'.'不是数值
* 当. 为第0位时,.的下一位必须是数字
* 当. 为第最后一位时,.的前一位必须是数字
* 当. 位于非首尾时,左右只要一方是数字即可
*/
if(decimalPoint || e || (i == 0 && (i == str.length - 1 || !isNum(str[i + 1])))
|| (i == str.length - 1 && !isNum(str[i - 1]))
|| (!isNum(str[i - 1]) && !isNum(str[i + 1]))) {
return false;
}
decimalPoint = true;
} else if(ch == 'e' || ch == 'E') {
/**
* e只能出现一次,且不能出现在首部和尾部
*/
if (e || i == 0 || i == str.length - 1) {
return false;
}
e = true;
} else if(ch < '0' || ch > '9') {
return false;
}
}
return true;
}
思路二
这个思路来源于剑指Offer上想法,基于分治的思想,也就是说把一个合法的数分成几部分,然后对各个部分进行合法检测。
根据题目可以得出一个合法的数字的组成为:A.BeA
或者.BeA
。其中A
表示可以带有符号的数字,B
表示不带符号的数字。我们只需对上述模型中分别进行不同的检验即可。
用一个标志位表示期间检验的合法性,我们首先判断小数点之前是否是可以带有符号的数字,碰到小数点后,判断是否是不可以带符号的数字,若碰到e,则判断之后是不是可以带有符号的数字。我们制定了检验规则,即依次判断是否有’.’和’e’,所以保证了这些符号只出现一次。最后我们判断标志位是否是true且已遍历完整个字符串即可。
可以带符号的数值与不可以带符号检验的代码其实相似,只不过了多了一步判断是否’+ -‘号。
static int index = 0;
/**
* 拆解法,或者称为分治法
* A.B[e|E]A
*
* @param str
* @return
*/
public static boolean isNumeric2(char[] str) {
if(str == null || str.length == 0) {
return false;
}
index = 0;
boolean result = scanInteger(str);
if(index < str.length && str[index] == '.') {
index++;
//因为.之前可以没有数字,所以用||
result = scanUnsignedInteger(str) || result;
}
if(index < str.length && (str[index] == 'e' || str[index] == 'E')) {
index++;
//e的前面和后面必须有数字,要保证e的前面合法,后面也合法,所以采用&&
result = result && scanInteger(str);
}
return result && index == str.length;
}
/**
* 扫描带符号的整数部分
* @param str
* @return
*/
public static boolean scanInteger(char[] str) {
if(index < str.length && (str[index] == '+' || str[index] == '-')) {
index++;
}
return scanUnsignedInteger(str);
}
/**
* 扫描不带符号的整数部分
* @param str
* @return
*/
public static boolean scanUnsignedInteger(char[] str) {
int start = index;
while(index < str.length && str[index] >= '0' && str[index] <= '9') {
index++;
}
return index > start;
}
此处举个例子帮助理解,比如123.4E-6。
- 首先判断是否是可以带符号的数字,符合
- 然后遇到小数点,判断之后是否符合数字,符合
- 碰到e,判断之后是否是可以带符号的数字,符合
- 标志位为true且已遍历,所以合法
总结
如果能够很好的找到合法数字的组成部分,那么就不用像思路一那么复杂了。