一、概述
輸入一個字符串,判斷其是否是一個合法的數字。
孤兒題目,邊界條件極其多,需要不斷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,在有罰時的情況下是很喫虧的。因此一定要注意全面思考。