【表示数值的字符串】

博客围绕判断字符串是否表示数值的算法展开。先给出题目描述,列举数值与非数值的示例。接着进行题目解析,引入状态和状态转移的概念,阐述状态列举和定义的法则,得到状态转移图并简化。还给出两种代码实现,最后引出枚举类及C++关联容器基本操作的基础问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

数值(按顺序)可以分成以下几个部分:

  1. 若干空格
  2. 一个 小数 或者 整数
  3. (可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数
  4. 若干空格

小数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符(‘+’ 或 ‘-’)
  2. 下述格式之一:
    1. 至少一位数字,后面跟着一个点 ‘.’
    2. 至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
    3. 一个点 ‘.’ ,后面跟着至少一位数字

整数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符(‘+’ 或 ‘-’)
  2. 至少一位数字

部分数值列举如下:
[“+100”, “5e2”, “-123”, “3.1416”, “-1E-16”, “0123”]
部分非数值列举如下:
[“12e”, “1a3.14”, “1.2.3”, “±5”, “12e+5.4”]

题目解析

看到这道题我没有头绪,感觉很难理清楚逻辑。我知道遇到字符的时候,需要做判断。做完判断后,可能是非数值字符串,直接返回结果;如果不能判断,再继续读取下一个字符。这种朴素的想法,没想到有人给这种判断加了一个耳目一新的名字:状态。判断的过程叫做状态转移

定义一个状态——字符串当前的状态;状态转移的条件——读进了一个字符。

法则1:(如何列举可能的状态)
将问题的基本域列举出来。
对于本题,我们写一个最复杂的数值字符串,+213.67E-06,前后有空格,从左往右,依次可想有空格、符号位、数值域、小数点、E/e这5种基本的状态。但是这5种状态并不能完整地概括所有的状态转移。例如,考察数值域的状态,进入数值域是读入一个数字即可,但是数值域的转移却有可能发生变化。小数点前的数值后读入一个小数点是合法的,小数点后的数值读入一个小数点是非法的。读入小数点之后会出现两种不同的状态。
法则2:(如何正确地定义状态)
每一个状态的转移,不能受到其他状态的影响,只能根据输入来判断。如果收到其他状态的影响,则说明定义的状态独立性不够,需要拆分。

以小数点为例,如果读入一个小数点,会进入一个名为小数点的状态。在分析这个状态如何往外转移时,如果读入一个数字,是否能够进入数字域的状态?这个是值得商榷的。因为小数中,小数点后面是可以不接数字的,只要小数点前面有数字;同时,如果小数点前面没有数字,则小数点后必须有数字。如果小数点后读入一个’E/e’,就会无法判断如何转移了。因为,数后小数点是可以接’E/e’的;但是非数后小数点却不能直接接’E/e’,必须接数字,直接跟’E/e’是非法。

根据前面两个法则,可以划分成如下的状态转移图:

状态转移图
这种划分下有11个状态。注意到Start状态和串首空格态的输入和输出其实是一样的,故可以简化成10个状态。

代码1:使用map来存储输入字符 -> 转移状态。这个代码得搭配上图才能比较好的理解,状态1,…,10使用数字来表示的,无法表示直观的含义。

        class Solution {
        public:
            bool isNumber(string s) {
                vector<unordered_map<char, int>> statesMap;
                statesMap.emplace_back(unordered_map<char, int>{{'b',1}, {'s', 2}, {'d', 3}, {'c', 5}});        // state 0: start
                statesMap.emplace_back(unordered_map<char,int>{{'b', 1}, {'s',2}, {'d', 3},{'c',5}});           // state 1: 串首空格
                statesMap.emplace_back(unordered_map<char,int>{{'d', 3},{'c',5}});                                          // state 2: E前符号位
                statesMap.emplace_back(unordered_map<char,int>{{'d', 3}, {'c', 4}, {'e', 7}, {'b', 10}});       // state 3: 点前数字域
                statesMap.emplace_back(unordered_map<char,int>{{'d',6},{'b',10},{'e',7}});                            // state 4: 数后小数点
                statesMap.emplace_back(unordered_map<char,int>{{'d',6}});                                                          // state 5: 非数后小数点
                statesMap.emplace_back(unordered_map<char,int>{{'d', 6},{'b', 10},{'e', 7}});                          // state 6: 点后数字域
                statesMap.emplace_back(unordered_map<char,int>{{'s', 8},{'d',9}});                                            // state 7: E/e
                statesMap.emplace_back(unordered_map<char,int>{{'d',9}});                                                           // state 8: E后符号位
                statesMap.emplace_back(unordered_map<char,int>{{'d', 9}, {'b', 10}});                                         // state 9: E后数字域
                statesMap.emplace_back(unordered_map<char,int>{{'b',10}});                                                          // state 10: 串后字符串
                int state = 0;
                for (const auto &c : s) {
                    char flag = 'x';
                    if (c == ' ') {
                        flag = 'b';
                    }else if (c == '+' || c == '-') {
                        flag = 's';
                    }else if ( c == 'E' || c == 'e') {
                        flag = 'e';
                    }else if (c == '.') {
                        flag = 'c';
                    }else if (c >= '0' && c <= '9') {
                        flag = 'd';
                    }
                    if (statesMap[state].find(flag) == statesMap[state].end()) {
                        return false;
                    }else {
                        state = statesMap[state][flag];
                    }
                }
                return state != 1 && state != 2 && state != 5 && state != 7 && state != 8;
            }
        };

代码2:使用枚举类。

class Solution {
public:
    enum State {
        STATE_INITIAL,
        STATE_INT_SIGN,
        STATE_INTEGER,
        STATE_POINT,
        STATE_POINT_WITHOUT_INT,
        STATE_FRACTION,
        STATE_EXP,
        STATE_EXP_SIGN,
        STATE_EXP_NUMBER,
        STATE_END
    };

    enum CharType {
        CHAR_NUMBER,
        CHAR_EXP,
        CHAR_POINT,
        CHAR_SIGN,
        CHAR_SPACE,
        CHAR_ILLEGAL
    };

    CharType toCharType(char ch) {
        if (ch >= '0' && ch <= '9') {
            return CHAR_NUMBER;
        } else if (ch == 'e' || ch == 'E') {
            return CHAR_EXP;
        } else if (ch == '.') {
            return CHAR_POINT;
        } else if (ch == '+' || ch == '-') {
            return CHAR_SIGN;
        } else if (ch == ' ') {
            return CHAR_SPACE;
        } else {
            return CHAR_ILLEGAL;
        }
    }

    bool isNumber(string s) {
        unordered_map<State, unordered_map<CharType, State>> transfer{
            {
                STATE_INITIAL, {
                    {CHAR_SPACE, STATE_INITIAL},
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_POINT, STATE_POINT_WITHOUT_INT},
                    {CHAR_SIGN, STATE_INT_SIGN}
                }
            }, {
                STATE_INT_SIGN, {
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_POINT, STATE_POINT_WITHOUT_INT}
                }
            }, {
                STATE_INTEGER, {
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_POINT, STATE_POINT},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_POINT, {
                    {CHAR_NUMBER, STATE_FRACTION},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_POINT_WITHOUT_INT, {
                    {CHAR_NUMBER, STATE_FRACTION}
                }
            }, {
                STATE_FRACTION,
                {
                    {CHAR_NUMBER, STATE_FRACTION},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_EXP,
                {
                    {CHAR_NUMBER, STATE_EXP_NUMBER},
                    {CHAR_SIGN, STATE_EXP_SIGN}
                }
            }, {
                STATE_EXP_SIGN, {
                    {CHAR_NUMBER, STATE_EXP_NUMBER}
                }
            }, {
                STATE_EXP_NUMBER, {
                    {CHAR_NUMBER, STATE_EXP_NUMBER},
                    {CHAR_SPACE, STATE_END}
                }
            }, {
                STATE_END, {
                    {CHAR_SPACE, STATE_END}
                }
            }
        };

        int len = s.length();
        State st = STATE_INITIAL;

        for (int i = 0; i < len; i++) {
            CharType typ = toCharType(s[i]);
            if (transfer[st].find(typ) == transfer[st].end()) {
                return false;
            } else {
                st = transfer[st][typ];
            }
        }
        return st == STATE_INTEGER || st == STATE_POINT || st == STATE_FRACTION || st == STATE_EXP_NUMBER || st == STATE_END;
    }
};

引伸基础问题:

  1. 什么是枚举类?
  2. C++中的关联容器基本操作:1) 如何判断是否含有key? 2)如何通过Key查找value? 2)如何添加key-value?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值