[剑指-Offer] 20. 表示数值的字符串(多情况分析、分类讨论、代码优化)

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', 还要加上正负号 '+/-', 除了这些字符需要考虑意外,出现了任何其他的字符,可以马上判定不是数字。下面来一 一分析这些出现了也可能是数字的特殊字符:

  1. 空格 ‘ ’: 空格分为两种情况需要考虑,一种是出现在开头和末尾的空格,另一种是出现在中间的字符。出现在开头和末尾的空格不影响数字,而一旦中间出现了空格,则立马不是数字。解决方法预处理时去掉字符的首位空格,中间再检测到空格,则判定不是数字。

  2. 小数点 '.':小数点需要分的情况较多,首先的是小数点只能出现一次,但是小数点可以出现在任何位置,开头 ".3",中间 "1.e2", 以及结尾 "1.", 而且需要注意的是,小数点不能出现在自然数 'e/E' 之后,如 "1e.1" false"1e1.1" false。还有,当小数点位于末尾时,前面必须是数字,如 "1." true" -." false解决方法:开头中间结尾三个位置分开讨论情况。

  3. 自然数 'e/E':自然数的前后必须有数字,即自然数不能出现在开头和结尾,如 "e" false".e1" false"3.e" false"3.e1" true。而且小数点只能出现在自然数之前,还有就是自然数前面不能是符号,如 "+e1" false"1+e" false解决方法:开头中间结尾三个位置分开讨论情况。

  4. 正负号 '+/-",正负号可以再开头出现,可以再自然数 e 之后出现,但不能是最后一个字符,后面得有数字,如 "+1.e+5" true解决方法:开头中间结尾三个位置分开讨论情况。

下面我们开始正式分开头中间结尾三个位置来讨论情况:

  1. 在讨论三个位置之前做预处理,去掉字符串首尾的空格,可以采用两个指针分别指向开头和结尾,遇到空格则跳过,分别指向开头结尾非空格的字符

  2. 对首字符处理,首字符只能为数字或者正负号 '+/-',我们需要定义三个 flag 在标示我们是否之前检测到过小数点,自然数和正负号。首字符如为数字或正负号,则标记对应的 flag,若不是,直接返回 false

  3. 对中间字符的处理,中间字符会出现五种情况,数字,小数点,自然数,正负号和其他字符

  • 若是数字,标记 flag 并通过

  • 若是自然数,则必须是第一次出现自然数,并且前一个字符不能是正负号,而且之前一定要出现过数字,才能标记 flag 通过

  • 若是正负号,则之前的字符必须是自然数 e,才能标记 flag 通过

  • 若是小数点,则必须是第一次出现小数点并且自然数没有出现过,才能标记 flag 通过

  • 若是其他,返回 false

  1. 对尾字符处理,最后一个字符只能是数字或小数点,其他字符都返回 false
  • 若是数字,返回 true

  • 若是小数点,则必须是第一次出现小数点并且自然数没有出现过,还有前面必须是数字,才能返回 true

至此,if-else 可以解决这个问题了。


再分析优化:

上面的写法略为复杂,我们尝试着来优化一下,根据上面的分析,所有的字符可以分为六大类:空格,符号,数字,小数点,自然底数和其他字符,我们需要五个标志变量,num, dot, exp, sign 分别表示数字,小数点,自然底数和符号是否出现,numAfterE 表示自然底数后面是否有数字,那么我们分别来看各种情况:

  • 空格: 我们需要排除的情况是,当前位置是空格而后面一位不为空格,但是之前有数字,小数点,自然底数或者符号出现时返回 false

  • 符号:符号前面如果有字符的话必须是空格或者是自然底数,标记 signtrue

  • 数字:标记 numnumAfterEtrue

  • 小数点:如果之前出现过小数点或者自然底数,返回 false,否则标记 dottrue

  • 自然底数:如果之前出现过自然底数或者之前从未出现过数字,返回 false,否则标记 exptruenumAfterEfalse

  • 其他字符:返回 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;
    }
};
发布了307 篇原创文章 · 获赞 125 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104565592