文章目錄
- 題目
- 思路一
- 代碼一
- 思路二
- 代碼二
題目
思路一
考察有限狀態自動機(參考jyd):
字符類型:
空格 「 」、數字「 0—9 」 、正負號 「 ± 」 、小數點 「 . 」 、冪符號 「 eE 」 。
狀態定義:
按照字符串從左到右的順序,定義以下 9 種狀態:
- 開始的空格
- 冪符號前的正負號
- 小數點前的數字
- 小數點、小數點后的數字
- 當小數點前為空格時,小數點、小數點后的數字
- 冪符號
- 冪符號后的正負號
- 冪符號后的數字
- 結尾的空格
結束狀態:
合法的結束狀態有 2, 3, 7, 8 。
算法流程:
-
初始化:
- 狀態轉移表 maps : 設 maps[i] ,其中 i 為所處狀態(9種之一), maps[i] 使用哈希表存儲可轉移至的狀態。鍵值對 (key, value) 含義:若此時的字符是 key ,則可從狀態 i 轉移至狀態 value 。
- 當前狀態 p : 起始狀態初始化為 p = 0 。
-
狀態轉移循環: 遍歷字符串 s 的每個字符 c 。
-
記錄字符類型 t : 分為四種情況。
- 當 c 為正負號時,執行
t = 's'
; - 當 c 為數字時,執行
t = 'd'
; - 當 c 為 e , E 時,執行
t = 'e'
; - 當 c 為 . , 空格 時,執行
t = c
(即用字符本身表示字符類型); - 否則,執行 t = ‘?’ ,代表為不屬于判斷范圍的非法字符,后續直接返回 false。
- 當 c 為正負號時,執行
-
終止條件: 若字符類型 t 不在哈希表 maps[p] 中,說明無法轉移至下一狀態,因此直接返回 False 。
-
狀態轉移: 狀態 p 轉移至
maps[p][t]
。
-
-
返回值: 跳出循環后,若狀態
p∈2,3,7,8
,說明結尾合法,返回 True ,否則返回 False 。
再詳細說一下狀態轉移表的作用(下面代碼部分的注釋中有結合實例進行詳解):
- 處于第 i 行表明此時遍歷到的字符是第 i+1 種狀態(可以對照上文的狀態定義)
- 那么我下一個字符可以是第 i 行中的各個 key 對應的字符。
- 如果某字符沒有寫進第 i 行,表示 i+1 狀態的下一個字符不應是某字符
復雜度分析:
- 時間復雜度 O(N) : 其中 N 為字符串 s 的長度,判斷需遍歷字符串,每輪狀態轉移的使用 O(1) 時間。
- 空間復雜度 O(1) : maps 和 p 使用常數大小的額外空間。
代碼一
class Solution {
public:bool isNumber(string s) {vector<map<char,int>> maps = {
// 狀態轉移表
// 以第一行為例,狀態轉移表的含義為:
// 開始的空格其下一個字符可以繼續是空格,也可以是符號、數字、小數點,
// 但不可是第0行不存在的e。
// 也就是為了保證字符串是數值,空格后面不能直接跟一個冪符號。{{' ', 0}, {'s', 1}, {'d', 2}, {'.', 4}}, // 開始的空格{{'d', 2}, {'.', 4}}, // 冪符號前的正負號{{'d', 2}, {'.', 3}, {'e', 5}, {' ', 8}}, // 小數點前的數字{{'d', 3}, {'e', 5}, {' ', 8}}, // 小數點、小數點后的數字{{'d', 3}}, // 當小數點前為空格時,小數點、小數點后的數字{{'s', 6}, {'d', 7}}, // 冪符號{{'d', 7}}, // 冪符號后的正負號{{'d', 7}, {' ', 8}}, // 冪符號后的數字{{' ', 8}} // 結尾的空格};int p = 0; //起始狀態char t;for(char c : s) {if(c >= '0' && c <= '9') t = 'd';else if(c == '+' || c == '-') t = 's';else if(c == 'e' || c == 'E') t = 'e';else if(c == '.' || c == ' ') t = c;else t = '?';if(maps[p].find(t) == maps[p].end()) return false;p = maps[p][t];}return p == 2 || p == 3 || p == 7 || p == 8;}
};
思路二
用 bool 類型變量 ret 存儲搜索過程中的結果,整個搜索過程如下:
- 過濾首部空格
- 過濾正負號,緊接著檢查是否有數字
- 遇到第一個不是數字的字符,應為
'.'
或者e
'.'
的后面可以有e
,但e
的后面不能有'.'
,所以先處理'.'
再處理e
'.'
前面可以什么都沒有,后面也可以什么都沒有,只要有一邊有數據就行e
的前后必須都要有數據,并且后面可以存在正負號,所以需要過濾正負號- 過濾尾部空格
- 檢查 ret 狀態以及是否已經遍歷完字符串
關于第8點檢查是否已經遍歷完字符串:
因為如果字符串是數值,尾部空格應該是字符串的末尾部分倒數幾個字符,空格完了字符串應該也就結束了。如果過濾完了尾部空格還有字符,說明該字符串不是數值。
代碼二
class Solution {
public:bool scanDigit(string s, int& i){int count = i;while(i != s.size()){if(isdigit(s[i]))++i;elsebreak;}return i > count;//如果數據中沒有一個數字,則直接返回false,有則返回true}bool scanSign(string s, int& i){if(i < s.size() && s[i] == '+' || s[i] == '-'){++i;}//過濾正負號return scanDigit(s, i);}bool isNumber(string s) {if(s.empty())return false;int i = 0;while(s[i] == ' ')++i;//過濾首部空格bool ret = scanSign(s, i);//第一遍搜索,先走完.或者e前的所有數字//.的后面可以有e,但e的后面不能有.,所以先處理.再處理eif(i < s.size() && s[i] == '.'){ret = scanDigit(s, ++i) || ret;//.前面可以什么都沒有,后面也可以什么都沒有,只要有一邊有數據就行}if(i < s.size() && (s[i] == 'e' || s[i] == 'E')){ret = scanSign(s, ++i) && ret;//e的前后必須都要有數據,并且e后面可以存在正負號,所以需要過濾正負號}while(s[i] == ' ')++i;//因為空格只能出現在末尾和首部,過濾空格return ret && (i == s.size());//當e后面有數據,并且字符串全部走完,說明數據成立}
};