【LeetCode】65.Valid Number DFA应用

一、概述

输入一个字符串,判断其是否是一个合法的数字。

孤儿题目,边界条件极其多,需要不断submit来一个一个找出来,我错了二十多次,简直气死。

之前没用过DFA,DFA还是很好用的,Debug的时候很直观很容易,打补丁就好了。但是打补丁并不是一个好主意,会让代码可读性变得很差,不优美。因此如何设计状态,让状态与条件判断达到一个平衡,是一个技术活。

二、分析

1、我的方法

咱们先来看看,最初我想法很简单,因此状态转移图也很简单。

直到我发现.5时正确的,事情开始不对了:

然后开始疯狂打补丁:

为什么要这么多补丁呢?

主要是因为.5是正确的,3.是正确的,但是.自己是错误的,我原来就认为3.5这种是正确的,前面两者都是错误的——所以才弄错。另外,没有考虑空格也是一个原因——但是空格的状态是不能省略的。

然后呢?没考虑.3.5是错误的,e3e5是错误的,3.e5是正确的,3e3.5是错误的,3e3.是错误的——反正就一大堆错。最后弄出这么个丑陋的代码:

class Solution {
public:
    bool isNumber(string s) {
        bool res=true;
        int flag=0;
        int point=0;
        int e=0;
        for(int i=0;i<s.size();++i)
        {
            if(flag==0)
            {
                if((s[i]=='+'||s[i]=='-')&&i!=s.size()-1)
                    flag=1;
                else if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]=='.'&&i!=s.size()-1)
                {
                    flag=6;
                    point=1;
                }
                else if(s[i]==' '&&i!=s.size()-1)
                    flag=0;
                else
                    return false;
            }
            else if(flag==1)
            {
                if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]=='.'&&point!=1&&i!=s.size()-1)
                {
                    if(point==0)
                    {
                       flag=4;
                        point=1;
                    }
                    else
                        return false;
                    
                }
                else
                    return false;
            }
            else if(flag==2)
            {
                if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]=='e'&&i!=s.size()-1)
                {
                    if(e==0)
                    {
                        flag=3;
                        e=1;
                    }
                    else
                        return false;
                    
                }
                else if(s[i]=='.'&&point!=1&&e!=1)
                {
                    if(point==0)
                    {
                       flag=4;
                        point=1;
                    }
                    else
                        return false;
                    
                }
                else if(s[i]==' ')
                    flag=5;
                else
                    return false;
            }
            else if(flag==3)
            {
                if((s[i]=='+'||s[i]=='-')&&i!=s.size()-1)
                    flag=1;
                else if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else
                    return false;
            }
            else if(flag==4)
            {
                if(e>0)
                    return false;
                else if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else if(s[i]==' ')
                    flag=5;
                else if(s[i]=='e'&&i!=s.size()-1)
                {
                    if(e==0)
                    {
                       flag=3;
                        e=1;
                    }
                    else
                        return false;
                }
                else
                    return false;
            }
            else if(flag==5)
            {
                if(s[i]==' ')
                    flag=5;
                else
                    return false;
            }
            else if(flag==6)
            {
                if(s[i]>='0'&&s[i]<='9')
                    flag=2;
                else
                    return false;
            }
        }
        return true;
    }
};

总结起来就是:

+-不能在最后一个;

.可以在最后一个,但是.不能单独出现,.只能有一个,.不能在e后面;

空格只有在首尾出现才是合法的;

e只能有一个,e不能是最后一个。

这些题设中都没说!!!

2、较好的方法

我的方法是先判断当前状态,再判断该位是什么,然后转移状态;较好的方法是先判断该位是什么,再判断当前状态,然后转移状态。其实这本身没什么好不好,关键是人家对状态数和条件判断做了很好的平衡。

该方法首先将首尾的空格去掉,那么遇到空格就可以直接返回false,这样来讲,我的状态就只有6个,而他有八个状态,那么就比我多了两个状态,相应的,他的条件判断就简洁了很多。简而言之,就是用两个状态代替了我的e和point。状态转移如下图:

该方法与图片均来自该网址

仔细观察可知,在状态s3,即e的后面,出现数字的时候,他并没有选择返回s2,而是走向s5,同理,出现+或-的时候,并没有返回s1,而是走向s4。然后,在s6的时候,出现数字,转向s7而不是s2。最后,他指定了四个状态为最后的正确状态。只有循环结束时状态为这四个才是正确的。

PS:s6转向s3仅在.之前有数字的时候才是正确的。下文中的flag就是为了这种情况。

代码如下:

class Solution {
public:
    bool isNumber(string str) {
        int state=0, flag=0; // flag to judge the special case "."
        while(str[0]==' ')  str.erase(0,1);//delete the  prefix whitespace 
        while(str[str.length()-1]==' ') str.erase(str.length()-1, 1);//delete the suffix whitespace
        for(int i=0; i<str.length(); i++){
            if('0'<=str[i] && str[i]<='9'){
                flag=1;
                if(state<=2) state=2;
                else state=(state<=5)?5:7;
            }
            else if('+'==str[i] || '-'==str[i]){
                if(state==0 || state==3) state++;
                else return false;
            }
            else if('.'==str[i]){
                if(state<=2) state=6;
                else return false;
            }
            else if('e'==str[i]){
                if(flag&&(state==2 || state==6 || state==7)) state=3;
                else return false;
            }
            else return false;
        }
        return (state==2 || state==5 || (flag&&state==6) || state==7);
    }
};

三、总结

DFA原理不难,难的是把所有转移情况都想到,同时保证状态数和条件判断的平衡。

虽然debug很方便,但要是不注意,就会需要很多次试错才能AC,在有罚时的情况下是很吃亏的。因此一定要注意全面思考。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章