10. 正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/regular-expression-matching
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
拿到这个问题,想了想,这个难度是困难,感觉也有点绕。分析了一下,感觉可以从头开始一个个字符串匹配,唯一要处理的是p的*和.,思路就是,如果匹配,s和p都跳到下一个,不匹配,就是出错了。
这样需要加上*,如果当前匹配,s下一个与当前一样,p下一个是*,那么也是合适的。但是这是出现一个问题,比如s=sssa p=s*ssa,这样我无法区分*到底要匹配到哪里。
所以想是否可以逆向匹配呢?结果分析了一下,还是没解决这个问题,比如s=sssa p=sss*a,还是没办法区分*到底匹配多少。
这时发现,一切原因都归于在*的前后又出现了相同的字符串,那么是不是可以先把p变换一下呢把s*ssa或者sss*a都变成s*a,就是把相同的合并。
这样写了测试代码,提交发现有问题,分析后,突然发现这里面最影响分析,最影响性能,并且把我的这个方法变成无效的规则['.' 匹配任意单个字符],这里有个组合[".*" 表示可匹配零个或多个('*')任意字符('.')],会导致回到了上面的问题到底我要匹配到哪?示例:
s=aaabcd p=a.*abcd
怎么办呢,看来只能在这种方式下穷举了。拿上面的举例,在匹配到.*的时候,就需要判断.*不匹配任何字符,匹配一个字符,匹配多个相同字符分别是否成功,成功,则这种匹配规则是对的。
我的解一,看到题解中,有一个有限状态机的解法,感觉挺好的,也很好理解,然后按照那个提示写了一个所谓的穷举法。就是把匹配规则字符p按照类似的有限状态机的模式生成一个序列,每个节点定义了当前的匹配字符和是否可以重复(匹配0次或无限多次)。
如果当前节点不是可重复的,直接判断,如果匹配直接跳到下一个节点,不匹配就直接返回false;如果当前节点是可重复的,直接跳到下一个节点,如果这条分支返回false,就匹配当前节点一次,以此循环。
struct CHECKNODE { //当前字符 char c; //是否可以循环匹配0-n次 bool rep; //下一个字符节点 CHECKNODE* pnext; CHECKNODE() { c = 0; rep = false; pnext = nullptr; } }; bool checkm(const string& s, int sindex, CHECKNODE* pnow) { if (sindex == s.size()) { if (pnow == nullptr) { return true; } else { if (pnow->rep) { return checkm(s, sindex, pnow->pnext); } else { return false; } } } if (sindex < s.size() && pnow == nullptr) { return false; } if (pnow->rep) { if (!checkm(s, sindex, pnow->pnext)) { if (pnow->c == s[sindex] || pnow->c == '.') { return checkm(s, sindex + 1, pnow); } else { return false; } } else { return true; } } else { if (pnow->c == s[sindex] || pnow->c == '.') { return checkm(s, sindex + 1, pnow->pnext); } else { return false; } } return false; } bool isMatch(string s, string p) { CHECKNODE* pheard = nullptr; CHECKNODE* pnow = nullptr; for (auto& iter : p) { if (pheard == nullptr) { pheard = new CHECKNODE(); pnow = pheard; pnow->c = iter; } else { if (iter != '*') { pnow->pnext = new CHECKNODE(); pnow = pnow->pnext; pnow->c = iter; } else { pnow->rep = true; } } } return checkm(s, 0, pheard); }
我的解法二,本以为上面创建了一个列表,内存和速度会受影响,改成了直接用引用和索引判断,结果基本没什么提升
class Solution { public: bool checkm(const string& s, int sindex, const string& p, int pindex) { if (sindex == s.size()) { if (pindex == p.size()) { return true; } else { if (pindex + 1 < p.size() && p[pindex + 1] == '*') { return checkm(s, sindex, p, pindex + 2); } else { return false; } } } if (sindex < s.size() && pindex == p.size()) { return false; } if (pindex + 1 < p.size() && p[pindex + 1] == '*') { if (!checkm(s, sindex, p, pindex + 2)) { if (p[pindex] == s[sindex] || p[pindex] == '.') { return checkm(s, sindex + 1, p, pindex); } else { return false; } } else { return true; } } else { if (p[pindex] == s[sindex] || p[pindex] == '.') { return checkm(s, sindex + 1, p, pindex + 1); } else { return false; } } return false; } bool isMatch(string s, string p) { return checkm(s, 0, p, 0); } };
我的解三,看到评论,可以使用动态规划,就是把一些结果保存下来,避免下次再次判断。ret[si][pi],这里面保存了,到sindex和pindex之前的匹配结果。因为在遍历中有很多需要重复判断,保存了结果就可以直接返回减少了运算。
比如,a*可以匹配,也可以不匹配,那么.*就会判断两次是否匹配aab中的第二个a,比如a*匹配第一个a,.*匹配第二个a;或是a*不匹配,.*匹配第一个和第二个a,这样就出现了重复判断,我们可以在第一次的判断得出结果后保存下来,在第二次判断的时候直接返回。由于比较了用索引和链表的优劣势,决定还是用链表。一是好理解,二是速度更优。
struct CHECKNODE { //当前字符 char c; //是否可以循环匹配0-n次 bool rep; //下一个字符节点 CHECKNODE* pnext; int pindex; CHECKNODE() { c = 0; rep = false; pnext = nullptr; pindex = 0; } }; bool checkm(const string& s, int sindex, CHECKNODE* pnow, char** aret) { bool ret = false; if (pnow != nullptr && aret[sindex][pnow->pindex] != -1) { ret = aret[sindex][pnow->pindex]; } else { if (sindex == s.size()) { if (pnow == nullptr) { ret = true; } else { if (pnow->rep) { ret = checkm(s, sindex, pnow->pnext, aret); } else { ret = false; } } } else if (sindex < s.size()) { if (pnow == nullptr) { ret = false; } else { if (pnow->rep) { if (!checkm(s, sindex, pnow->pnext, aret)) { if (pnow->c == s[sindex] || pnow->c == '.') { ret = checkm(s, sindex + 1, pnow, aret); } else { ret = false; } } else { ret = true; } } else { if (pnow->c == s[sindex] || pnow->c == '.') { ret = checkm(s, sindex + 1, pnow->pnext, aret); } else { ret = false; } } } } if (pnow != nullptr) { aret[sindex][pnow->pindex] = ret; } } return ret; } bool isMatch(string s, string p) { CHECKNODE* pheard = nullptr; CHECKNODE* pnow = nullptr; int pindex = -1; for (auto& iter : p) { if (pheard == nullptr) { pindex++; pheard = new CHECKNODE(); pnow = pheard; pnow->c = iter; pnow->pindex = pindex; } else { if (iter != '*') { pindex++; pnow->pnext = new CHECKNODE(); pnow = pnow->pnext; pnow->c = iter; pnow->pindex = pindex; } else { pnow->rep = true; } } } char** aret = new char*[s.size() + 1]; for (int i = 0; i < s.size() + 1; i++) { aret[i] = new char[pindex + 1]; } for (int i = 0; i < s.size() + 1; i++) { for (int j = 0; j < pindex + 1; j++) { aret[i][j] = -1; } } int ret = checkm(s, 0, pheard, aret); for (int i = 0; i < s.size() + 1; i++) { delete[]aret[i]; } delete[]aret; return ret; }