版权声明:本文为博主原创学习笔记,如需转载请注明来源。 https://blog.csdn.net/SHU15121856/article/details/82425798
面试题19:正则表达式匹配
请实现一个函数用来匹配包含’.’和’*’的正则表达式。模式中的字符’.’表示任意一个字符,而’*’表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”ab*ac*a”匹配,但与”aa.a”及”ab*a”均不匹配。
基本可以看成编译原理的parser问题,但是找出文法不容易,也没必要为每种情况写个匹配的函数(递归下降),可以画个NFA分析一下,然后在递归里直接用字符串游标的移动记录匹配情况。
#include<bits/stdc++.h>
using namespace std;
bool matchCore(const char* str, const char* pattern);
// 参数:
// str: 要匹配的字符串首地址
// pattern: 要匹配的模式串首地址
// 返回值:
// 能否匹配成功
bool match(const char* str, const char* pattern) {
//非空校验
if(str == nullptr || pattern == nullptr)
return false;
//调用匹配的函数,递归地进行匹配
return matchCore(str, pattern);
}
// 参数:
// str: 要匹配的字符(子)串首地址
// pattern: 要匹配的模式(子)串首地址
// 返回值:
// 从此位置向后能否匹配成功
bool matchCore(const char* str, const char* pattern) {
if(*str == '\0' && *pattern == '\0')//如果两串都到达结尾
return true;//匹配成功
if(*str != '\0' && *pattern == '\0')//如果模式串先到达结尾
return false;//匹配一定失败
//另:如果字符串先到达结尾,不一定匹配失败,因为可能模式串剩下'a*'或者'*'可以匹配空
//如果模式串下一字符是'*',那么会影响当前字符的匹配长度
if(*(pattern + 1) == '*') {
//如果当前模式是和字符串匹配的,或者当前模式是'.'可匹配任一字符
if(*pattern == *str || (*pattern == '.' && *str != '\0'))//即如果当前是匹配的
//考虑三种匹配方式
//跳过当前字符串上的'a',跳过模式串'a*',即'*'使'a'重复1次
return matchCore(str + 1, pattern + 2)
//跳过当前字符串上的'a',期望后面的'a'还能匹配'a*',即'*'使'a'重复>1次
|| matchCore(str + 1, pattern)
//跳过模式串'a*',即'*'使'a'重复0次
|| matchCore(str, pattern + 2);
else//如果当前模式不能匹配字符串上的这个字符
//这时这个'*'号只能起到使前面的字符'a'重复0次的效果
return matchCore(str, pattern + 2);
}
//以下是后面没有跟'*'的情况
//如果当前模式匹配字符串该位置的字符('a'-'a'匹配或者'a'-'.'匹配)
if(*str == *pattern || (*pattern == '.' && *str != '\0'))
return matchCore(str + 1, pattern + 1);//匹配,两个串都前进一步
//至此还没有返回,说明这个(子)匹配已经失败
return false;
}
int main(){
cout<<boolalpha<<match("aaca","ab*a*c*a")<<endl;
return 0;
}
面试题20:表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串“+100”、“5e2”、“-123”、“3.1416”及“-1E-16”都表示数值,但“12e”、“1a3.14”、“1.2.3”、“+-5”及“12e+5.4”都不是。
表示数值的字符串遵循模式A[.[B]][e|EC] | .B[e|EC]
。其中A
是数值的整数部分,B
是数值的小数部分,C
是数值的整数部分,e
和E
是终结符表示带科学计数法(10的几次幂),A
和C
能以终结符+
和-
开头,也可以像B
一样是普通的数位串。
#include<bits/stdc++.h>
using namespace std;
bool scanUnsignedInteger(const char** str);
bool scanInteger(const char** str);
//数字的格式可以用A[.[B]][e|EC]或者.B[e|EC]表示,其中A和C都是
//整数(可以有正负号,也可以没有),而B是一个无符号整数
bool isNumeric(const char* str) {
//输入非空校验
if(str == nullptr)
return false;
//在开头尝试扫描一下有符号整数
//如果扫描到就扫完了A,接下来要遇到.B或者指数部分或者啥也没有
//如果没扫到,接下来就必须要遇到.B
bool numeric = scanInteger(&str);//在这个函数内会移动str(字符串的一级指针)
//如果出现'.',则接下来是数字的小数部分,即B部分
if(*str == '.') {
++str;//越过这个'.'
//如果之前numeric为真,说明扫过了A,即便有.有没有B也无所谓,如'233.'即233.0合法
//没扫过A时,一定要扫'.'然后扫B部分,扫B部分用无符号整数,如'.233'即0.233合法
numeric = scanUnsignedInteger(&str) || numeric;
}
//如果没有扫到A也没有出现'.',这时候已经非法了
//可以在这里直接返回,但后面的代码也适用,不会将false变成true
//书上就是没有直接返回,接着用后面的代码,因为都是与运算,所以没关系
//总之,到现在为止如果numeric为true最后不一定true,但是为false最后一定false了
//所以不妨加个剪枝提高效率
if(!numeric)
return false;
//注意!加了这个剪枝以后,后面实际上就没必要再和numeric做&&运算了,但这里还是保留书上的代码
//如果出现'e'或者'E',接下来跟着的是数字的指数部分
if(*str == 'e' || *str == 'E') {
++str;//越过这个'e'或者'E'
//下面一行代码用&&的原因:
//当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
//当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4
numeric = numeric && scanInteger(&str);
}
//当然也可以不出现指数部分,那么后面就要直接结束了
//结束时还要判断一下已经走到了字符串结尾,而不会有遗留的未匹配到的部分
return numeric && *str == '\0';
}
//扫描无符号整数,传入一个字符串位置的二级指针,扫描成功返回true
//(对于B直接调用它,因为B不能带符号)
bool scanUnsignedInteger(const char** str) {
const char* before = *str;//将当前扫描的起始地址记录下来
//只要没到字符串末尾,而且出现的都是'0'~'9'之间的字符
while(**str != '\0' && **str >= '0' && **str <= '9')
++(*str);//就一直向下扫描
return *str > before;//前面的循环至少进行一次才会大于,即整数必须存在
//那么对于可以不存在这种情况(如A),应由函数的调用方来维护
}
//扫描整数,传入一个字符串位置的二级指针,扫描成功返回true
//(对于A和C调用它,因为A和C是可以带符号的)
bool scanInteger(const char** str) {
if(**str == '+' || **str == '-')//如果以'+'或者'-'号开头
++(*str);//让其一级指针+1以跳过这个符号,它是允许出现在头部的符号,也可以不出现
//此时这个二级指针指向的是那个+1后的一级指针了
return scanUnsignedInteger(str);//调用扫描无符号整数的方法,还是传入这个二级指针就行
}
int main(){
cout<<boolalpha<<isNumeric("1.79769313486232E+308")<<endl;
cout<<boolalpha<<isNumeric(".e1")<<endl;
return 0;
}