前面研究OS的經歷實在是使人心力憔悴。。因此換個新鮮的,把本身的刷題感悟整理一番。刷了有些題了,就先拿最近幾天hard題打頭陣吧。首先說的是(065)Valid Number這個題,其實一眼看起來很簡單,不就是for/while/if/else嗎?那麼你可能不知道這道題其實有一個更加簡(bian)潔(tai)的方法,聽我慢慢道來。html
Validate if a given string is numeric.c++
Some examples: "0" => true " 0.1 " => true "abc" => false "1 a" => false "2e10" => truegit
Note: It is intended for the problem statement to be ambiguous. You should gather all requirements up front before implementing one.github
Java for LeetCode 065 Valid Number:正則表達式
public boolean isNumber(String s) { s = s.trim(); String[] splitArr = s.split("e"); if (s.length() == 0 || s.charAt(0) == 'e' || s.charAt(s.length() - 1) == 'e' || splitArr.length > 2) return false; for (int k = 0; k < splitArr.length; k++) { String str = splitArr[k]; boolean isDecimal = false; if (str.charAt(0) == '-' || str.charAt(0) == '+') str = str.substring(1); if (str.length() == 0) return false; for (int i = 0; i < str.length(); i++) { if ('0' <= str.charAt(i) && str.charAt(i) <= '9') continue; else if (str.charAt(i) == '.' && !isDecimal) { if (k == 0 && str.length() > 1) isDecimal = true; else return false; } else return false; } } return true; }
只要會點C++,那麼常規解法就不在話下,其實就是手動實現一遍itoa而已。算法
常規解法的優勢是:門檻低/常人寫得出/容易修改,也就是定製性好/擴展性差。ui
同時,它的缺點是:一旦驗證邏輯變複雜,那就gg了。好比我想把複數也算進去啊,那又得改那堆雜七雜八的代碼,使人感受不會再愛了。spa
正則表達式30分鐘入門教程總結得比較好。簡單來講,正則表達式(regex)能夠表示一個特定的詞法(編譯原理之詞法分析、語法分析、語義分析 - nic_r的專欄 - 博客頻道 - CSDN.NET),如整數、實數、複數、郵箱地址、電話號碼等。regex除了有匹配的功能以外,它還帶有替換/解析功能,這樣,可以知足涉及字符串操做的大多數需求。.net
好比,匹配方面,涉及用戶名匹配、郵箱地址的匹配等,若是這時你用常規解法就太臃腫、太麻煩了。替換/解析方面,如解析HTML/XML/JSON等,好比便捷。翻譯
那麼正則表達式與常規解法有什麼不一樣呢?
剛纔提到,常規解法雖然容易修改,但它的擴展性不足,我想更改一點需求,就要大刀闊斧改代碼,使人不會再愛。那麼如何解決這個擴展性的問題呢?那就須要將算法給抽象出來。
若是單用if/else/while/for作一個郵箱匹配,這時又須要作一個數字匹配功能,那麼這兩種代碼是八竿子打不着的,根本無法子複用代碼啊,怎麼辦呢?
其實稍微用腦子想想——你寫的爬蟲程序和他寫的遊戲程序也是風馬牛不相及吧?可是編譯器將它們翻譯成彙編語言後,是否是又有共同點了?好比都有Jump跳轉啊,有mov啊,類似度瞬間提升。這裏面的原理是什麼呢?原來雜七雜八的代碼間,經過編譯器的翻譯,居然變成了兩份差很少的彙編代碼(指用的指令大致類似)。那麼方法是翻譯嗎?
也就是說,原始的兩種內容不一樣的代碼,可能甲有着C++的高級特性,乙又是C寫的,它們翻譯成彙編後,用到的指令有99%都是相同的。反過來,若是我以彙編語言爲標準,來表示甲和乙,那麼這時候二者的代碼有99%是類似的。這時,咱們發現了可重用性!
回過頭來,想想,假若有一種語言a能夠表達數字、郵箱地址,那咱們就不須要再寫不一樣的C/C++代碼了,即:有一種機制將你的語言a的表達式翻譯成對應的代碼,運行這個代碼,能夠完成匹配工做。這不就是編譯器乾的事麼?
囉嗦了那麼多,其實意思就是:想要增長兩種功能不一樣的代碼之間的類似程度,必須從代碼中的相同點/不一樣點抽象出一種嶄新的語言,用這種嶄新的語言能夠以統一的語法形式來表達這兩份代碼。
而正則表達式,正是一種嶄新的語言。涉及正則表達式的語法、使用、解析,及NFA、DFA等知識這裏再也不贅述,請參閱專業書籍或是一些博客。
大體步驟是:
還好本身的https://github.com/bajdcc/jMinilang中有生成DFA的代碼。
正則匹配部分在priv.bajdcc.util.lexer.test.TestRegex,直接運行它,而後輸入上述正則表達式,那麼具體信息就出來了。
詳細信息(程序自動生成):
#### 正則表達式語法樹 #### 序列 { 循環{0,-1} { 字符 [\u0020,' '] } 循環{0,1} { 字符 [\u002b,'+'],[\u002d,'-'] } 分支 { 序列 { 循環{0,-1} { 字符 [\u0030,'0']-[\u0039,'9'] } 循環{0,1} { 字符 [\u002e,'.'] } 循環{1,-1} { 字符 [\u0030,'0']-[\u0039,'9'] } } 序列 { 循環{1,-1} { 字符 [\u0030,'0']-[\u0039,'9'] } 循環{0,1} { 字符 [\u002e,'.'] } 循環{0,-1} { 字符 [\u0030,'0']-[\u0039,'9'] } } } 循環{0,1} { 序列 { 字符 [\u0065,'e'] 循環{0,1} { 字符 [\u002b,'+'],[\u002d,'-'] } 循環{1,-1} { 字符 [\u0030,'0']-[\u0039,'9'] } } } 循環{0,-1} { 字符 [\u0020,' '] } } #### 狀態集合 #### [\u0020,' '] [\u002b,'+'] [\u002d,'-'] [\u002e,'.'] [\u0030,'0']-[\u0039,'9'] [\u0065,'e'] #### 最小化 #### 狀態[0] => 0, 邊 => [1] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 邊 => [0] 類型 => 字符區間 [\u0020,' '] 邊 => [2] 類型 => 字符區間 [\u002e,'.'] 邊 => [3] 類型 => 字符區間 [\u002b,'+'] 邊 => [3] 類型 => 字符區間 [\u002d,'-'] 狀態[1][結束] => 3,4,6, 邊 => [4] 類型 => 字符區間 [\u002e,'.'] 邊 => [5] 類型 => 字符區間 [\u0065,'e'] 邊 => [6] 類型 => 字符區間 [\u0020,' '] 邊 => [1] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 狀態[2] => 5, 邊 => [7] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 狀態[3] => 2, 邊 => [2] 類型 => 字符區間 [\u002e,'.'] 邊 => [1] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 狀態[4][結束] => 5,8, 邊 => [5] 類型 => 字符區間 [\u0065,'e'] 邊 => [6] 類型 => 字符區間 [\u0020,' '] 邊 => [4] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 狀態[5] => 10, 邊 => [8] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 邊 => [9] 類型 => 字符區間 [\u002b,'+'] 邊 => [9] 類型 => 字符區間 [\u002d,'-'] 狀態[6][結束] => 11, 邊 => [6] 類型 => 字符區間 [\u0020,' '] 狀態[7][結束] => 6, 邊 => [5] 類型 => 字符區間 [\u0065,'e'] 邊 => [7] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 邊 => [6] 類型 => 字符區間 [\u0020,' '] 狀態[8][結束] => 14, 邊 => [6] 類型 => 字符區間 [\u0020,' '] 邊 => [8] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] 狀態[9] => 13, 邊 => [8] 類型 => 字符區間 [\u0030,'0']-[\u0039,'9'] #### 狀態轉移矩陣 #### 0 3 3 2 1 -1 6 -1 -1 4 1 5 -1 -1 -1 -1 7 -1 -1 -1 -1 2 1 -1 6 -1 -1 -1 4 5 -1 9 9 -1 8 -1 6 -1 -1 -1 -1 -1 6 -1 -1 -1 7 5 6 -1 -1 -1 8 -1 -1 -1 -1 -1 8 -1
class Solution { inline int getCharMap(const char& c) { switch (c) { case ' ': return 0; case '+': return 1; case '-': return 2; case '.': return 3; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return 4; case 'e': return 5; } return -1; } public: bool isNumber(string s) { using t = int(*)[6]; int mm[] = { 0 ,3 ,3 ,2 ,1 ,-1, 6 ,-1 ,-1 ,4 ,1 ,5, -1 ,-1 ,-1 ,-1 ,7 ,-1, -1 ,-1 ,-1 ,2 ,1 ,-1, 6 ,-1 ,-1 ,-1 ,4 ,5, -1 ,9 ,9 ,-1 ,8 ,-1, 6 ,-1 ,-1 ,-1 ,-1 ,-1, 6 ,-1 ,-1 ,-1 ,7 ,5, 6 ,-1 ,-1 ,-1 ,8 ,-1, -1 ,-1 ,-1 ,-1 ,8 ,-1, }; auto m = (t)mm; bool final[] = {0, 1, 0, 0, 1, 0, 1, 1, 1, 0}; int status = 0; auto c = s.c_str(); for (;;) { auto local = *c++; int charClass = getCharMap(local); int refer = -1; if (charClass != -1) { refer = m[status][charClass]; } if (refer == -1) { return local == 0 && final[status]; } else { status = refer; } } } };