【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,在有罰時的情況下是很喫虧的。因此一定要注意全面思考。

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