【LeetCode】065-驗證數字

v2-cff4e31091fe0b54552a443b85a78df0_1200x500

寫在前面

前面研究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等知識這裏再也不贅述,請參閱專業書籍或是一些博客。

大體步驟是:

  1. 輸入正則表達式串pat
  2. 根據手寫的LL1解析pat,生成AST
  3. 根據AST構建NFA,添加Epsilon邊
  4. 從NFA轉換爲DFA,合併狀態,肯定終態
  5. DFA最小化,生成狀態轉移矩陣
  6. 根據狀態轉移矩陣進行匹配

輪子的用武之地

還好本身的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;
            }
        }
    }
};
https://zhuanlan.zhihu.com/p/25879478備份。
相關文章
相關標籤/搜索