【LeetCode】065-验证数字
写在前面
前面研究OS的经历实在是令人心力憔悴。。所以换个新鲜的,把自己的刷题感悟整理一番。刷了有些题了,就先拿最近几天hard题打头阵吧。首先说的是(065)Valid Number这个题,其实一眼看起来很简单,不就是for/while/if/else吗?那么你可能不知道这道题其实有一个更加简(bian)洁(tai)的方法,听我慢慢道来。
题目要求
Validate if a given string is numeric.
Some examples: "0" => true " 0.1 " => true "abc" => false "1 a" => false "2e10" => true
Note: It is intended for the problem statement to be ambiguous. You should gather all requirements up front before implementing one.
常规解法
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而已。
常规解法的优点是:门槛低/常人写得出/容易修改,也就是定制性好/扩展性差。
同时,它的缺点是:一旦验证逻辑变复杂,那就gg了。比如我想把复数也算进去啊,那又得改那堆杂七杂八的代码,令人感觉不会再爱了。
正则表达式
正则表达式30分钟入门教程总结得比较好。简单来说,正则表达式(regex)可以表示一个特定的词法(编译原理之词法分析、语法分析、语义分析 - nic_r的专栏 - 博客频道 - CSDN.NET),如整数、实数、复数、邮箱地址、电话号码等。regex除了有匹配的功能之外,它还带有替换/解析功能,这样,能够满足涉及字符串操作的大多数需求。
比如,匹配方面,涉及用户名匹配、邮箱地址的匹配等,如果这时你用常规解法就太臃肿、太麻烦了。替换/解析方面,如解析HTML/XML/JSON等,比如便捷。
那么正则表达式与常规解法有什么不同呢?
刚才提到,常规解法虽然容易修改,但它的扩展性不足,我想更改一点需求,就要大刀阔斧改代码,令人不会再爱。那么如何解决这个扩展性的问题呢?那就需要将算法给抽象出来。
如果单用if/else/while/for做一个邮箱匹配,这时又需要做一个数字匹配功能,那么这两种代码是八竿子打不着的,根本没法子复用代码啊,怎么办呢?
其实稍微用脑子想一想——你写的爬虫程序和他写的游戏程序也是风马牛不相及吧?但是编译器将它们翻译成汇编语言后,是不是又有共同点了?比如都有Jump跳转啊,有mov啊,相似度瞬间提高。这里面的原理是什么呢?原来杂七杂八的代码间,通过编译器的翻译,竟然变成了两份差不多的汇编代码(指用的指令大体相似)。那么方法是翻译吗?
也就是说,原始的两种内容不同的代码,可能甲有着C++的高级特性,乙又是C写的,它们翻译成汇编后,用到的指令有99%都是相同的。反过来,如果我以汇编语言为标准,来表示甲和乙,那么这时候两者的代码有99%是相似的。这时,我们发现了可重用性!
回过头来,想一想,假如有一种语言a可以表达数字、邮箱地址,那我们就不需要再写不同的C/C++代码了,即:有一种机制将你的语言a的表达式翻译成对应的代码,运行这个代码,可以完成匹配工作。这不就是编译器干的事么?
啰嗦了那么多,其实意思就是:想要增加两种功能不同的代码之间的相似程度,必须从代码中的相同点/不同点抽象出一种崭新的语言,用这种崭新的语言可以以统一的语法形式来表达这两份代码。
而正则表达式,正是一种崭新的语言。涉及正则表达式的语法、使用、解析,及NFA、DFA等知识这里不再赘述,请参阅专业书籍或是一些博客。
大致步骤是:
- 输入正则表达式串pat
- 根据手写的LL1解析pat,生成AST
- 根据AST构建NFA,添加Epsilon边
- 从NFA转换为DFA,合并状态,确定终态
- 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;
}
}
}
};
由https://zhuanlan.zhihu.com/p/25879478备份。
【LeetCode】065-验证数字的更多相关文章
- [LeetCode] Valid Number 验证数字
Validate if a given string is numeric. Some examples:"0" => true" 0.1 " => ...
- 前端与算法 leetcode 125. 验证回文串
目录 # 前端与算法 leetcode 125. 验证回文串 题目描述 概要 提示 解析 解法一:api侠 解法二:双指针 算法 传入测试用例的运行结果 执行结果 GitHub仓库 查看更多 # 前端 ...
- JS正则表达式验证数字
<script type="text/javascript"> function validate(){ var reg = new RegExp("^[0- ...
- JS正则表达式验证数字(很全)
1.<script type="text/javascript"> 2. function validate(){ 3. var reg = new ...
- JS正则表达式验证数字非常全
<script type="text/javascript"> function validate(){ var reg = new RegExp("^[0- ...
- JavaScript 【正则表达式验证数字代码】
可以看到 Ajax 请求多了个 x-requested-with ,可以利用它,request.getHeader("x-requested-with"); 为 null,则为传统 ...
- 正则表达式验证数字、汉字、电话号码,email,整数,浮点数
验证数字的正则表达式集 验证数字:^[0-9]*$验证n位的数字:^\d{n}$验证至少n位数字:^\d{n,}$验证m-n位的数字:^\d{m,n}$验证零和非零开头的数字:^(0|[1-9][0- ...
- C# 验证数字
/// <summary> /// 验证数字 /// </summary> /// <param name="number">数字内容</ ...
- Atitit 验证 数字验证 非空验证的最佳算法 h5
Atitit 验证 数字验证 非空验证的最佳算法 h5 <td><select class="searchBox-select" style=" ...
- 黄聪:JS正则表达式验证数字
<script type="text/JavaScript"> function validate(){ var reg = new RegExp( ...
随机推荐
- 数学图形(1.38)anguinea曲线
个人觉得,这是一种变异的SIN曲线. #http://www.mathcurve.com/courbes2d/anguinee/anguinee.shtml vertices = t = from ( ...
- 【转载】惠新宸:PHP在百度的应用现状及展望
http://blog.sina.com.cn/s/blog_645f8e970100qvd8.html 惠新宸,百度PHP高级顾问,年二十有八,好追根究底,有不良嗜好, 幸性本善.乙酉年识互联网,丁 ...
- C语言中register类型的变量有什么意义
int i; for(i=0; i<1000; i++){ // Some Code } 为了解决这个问题,可以将使用频繁的变量放在CPU的通用寄存器中,这样使用该变量时就不必访问内存,直接从寄 ...
- Java 中 方法名或类名 变更 同时 更新 所有引用的 类名或方法名 的解决方案
选中 类名,或属性名 Ctrl + 1 然后选择 理新当前文件,还是更新整个工作空间,然后修改对应的类名或方法名 回车即可. 如果.有SVN 版本在控制着,则 会提示,然后把对应的文件 锁定 再 ...
- ORA-00600 qerpxInitialize
今天早上巡检又见bug: A select query using a connect-by clause executing using parallel query may fail with O ...
- 使用javascript开发的视差滚动效果的云彩 极客标签 - 做最棒的极客知识分享平台
www.gbtags.com 使用javascript开发的视差滚动效果的云彩 阅读全文:使用javascript开发的视差滚动效果的云彩 极客标签 - 做最棒的极客知识分享平台
- Android provider中使用sqlite内存数据库
sqlite是支持内存数据库的,在Android中,我们可以通过provider实现内存数据库操作.内存数据库的优点,访问速度快,但在连接关闭后,数据库自动消失(在android中的表现是,provi ...
- T-SQL 之 触发器
触发器可以做很多事情,但也会带来很多问题.正确的使用在于在适当的时候使用,而不要在不适当的时候使用它们. 触发器的一些常见用途如下: [1] 弹性参照完整性:实现很多DRI不能实现的操作(例如,跨数据 ...
- js slice 参数为负值
示例代码 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF- ...
- C++(一)——HelloWorld
之前学C.学Python,学的比較多的是Java,作为大家口中更强大的C++,要学学,这次的话,以了解主要的特性和做个小游戏作为目标吧. 1)HelloWorld Eclipse执行C++之Launc ...
