每天一道LeetCode-----只可能有'.'和'*'的字符串正则匹配
来源:互联网 发布:自学编程视频网站 编辑:程序博客网 时间:2024/06/03 18:09
Regular Expression Matching
原题链接Regular Expression Matching
意思是模拟正则表达式的. *操作,判断给定的目标字符串p是否匹配源字符串s。
规则
- ‘.’匹配任意一个字符
- ‘*’匹配前一个字符0次或者多次,即可以删除它前面的字符
注:例子中isMatch(“ab”, “.“) == true说明”.“可以匹配的字符可以是任意组合,比如说此题中”.”匹配成”a”,那么因为 ” * ” 的前面是”.”,说明” * “可以匹配任意字符,不是说必须是前面”.”匹配的字符,也就是说”*”匹配的可以不是”.”匹配的”a”
假设s的长度为s_len,p的长度为p_len,那么可以分成比较s[0]和p[0],以及s[1 : s_len)和p[1 : p_len),再简化一点,如果p中没有” * “,可能会有” . “时
分析题目时可以分解成几种情况,
- (s[0] == p[0]) || (p[0] == ‘.’)。此时只需要比较s[1 : s_len)和p[1 : p_len)是否相等,递归即可实现
- (s[0] != p[0]) && (p[0] != ‘.’)。此时无需继续比较,返回false
如果p中有” * “,那么情况增多
- ((s[0] == p[0]) || (p[0] == ‘.’)) &&p[1] != ‘*’;此时和上面情况相同,比较下一个位置即可
- ((s[0] == p[0]) || (p[0] == ‘.’)) && p[1] == ‘‘;根据” “的规则,p[0]可以被删掉然后从p[2]开始和s[0]比较,这是一种情况。另一种是重复p[0]多次,多次的意思是p[0]后面可能有多个”p[0] * p[0] * p[0] * “,而模拟多次重复最好的方法就是不改变p的索引,仍然从p[0]开始比较s的下一个位置s[1]。
- ((s[0] != p[0]) && (p[0] != ‘.’)) && p[1] == ‘*’;显然直接删除p[0],从p[2]开始比较s[0]
- ((s[0] != p[0]) && (p[0] != ‘.’)) && p[1] != ‘*’;直接return fales,没得比了,当前这个位置就匹配不了
所以很多时候把所有情况列出来会很好的理清思路,这种当前状态可能会影响后续状态的问题是动态规划的典型问题,所以直接套用动态规划即可。
需要注意的就是一些边界条件,比方说i == s.size()的情况和j == p.size()的情况,又或者是j + 1 >= p.size()的情况,这种情况p[j + 1]不存在,就没办法判断后面的字符是否是’*’。
首先是递归的动态规划,dp[i][j]的意思是s[i : s_len)是否和p[j : p_len)匹配,递归时需要有三种状态
- 1,true
- 0,false
- -1,还没有被考虑
class Solution {public: bool isMatch(string s, string p) { /* 创建时数量多创建一个,即s.size() + 1和p.size() + 1 * 原因在于递归时i, j可能等于s.size()和p.size() */ std::vector<std::vector<int> > dp(s.size() + 1, std::vector<int>(p.size() + 1, -1)); return judge_match(0, 0, s, p, dp) == 1; }private: int judge_match(int i, int j, const std::string& s, const std::string& p, std::vector<std::vector<int> >& dp) { /* 如果dp[i][j] != -1,说明s[i : s_len)是否和p[j : p_len)匹配已经判断过,直接返回 * 这个位置就容易越界,是上面创建vector时数量加1的原因 */ if(dp[i][j] != -1) return dp[i][j]; /* 如果s和p都已经到达尾端,s没有字符需要匹配,p也没有可匹配的字符,直接返回true * 为什么需要判断p也到尾端? * 如果p未到尾端,说明p可能字符比s多,同样可能是无法匹配的,所以不能直接返回 * 只是可能无法匹配的原因 * p后面的每一个字符都可以被后面的'*'删掉,从而和s完全匹配 */ if(j >= p.size() && i >= s.size()) return 1; /* 如果p到达尾端而s未到,说明s剩余的字符p已经没有字符可以匹配了,返回false */ else if(j >= p.size() && i < s.size()) return 0; bool ans; bool first_match = (i < s.size()) && ((s[i] == p[j]) || (p[j] == '.')); /* 判断p[j]后面的字符是否是'*',从而有删掉p[j]和重复p[j]两种可能 * 注:重复p[j]的前提是s[i] == p[j] || p[j] == '.',否则s[i]无法匹配,只能删除p[j] */ if(j + 1 < p.size() && p[j + 1] == '*') ans = (first_match && judge_match(i + 1, j, s, p, dp) == 1) || (judge_match(i, j + 2, s, p, dp) == 1); else ans = (first_match && judge_match(i + 1, j + 1, s, p, dp) == 1); dp[i][j] = ans ? 1 : 0; return dp[i][j]; }};
然后是非递归的,非递归的动态规划很重要,真…的…很重要,
逐层向目标接近,有个选择两层for循环的界限的技巧,初始时是离目标最远的状态
比如说如果目标是dp[0][0],那么初始时就是dp[s_len - 1][p_len - 1],如果目标是dp[0][p_len],那么初始时就是dp[s_len][0]
但是,需要考虑边界情况,比如说本题就需要考虑dp[s_len][0],dp[s_len][1]…dp[s_len][p_len - 1],原因在递归中也体现了,就是如果s到了尾端而p没有到,也是有可能匹配成功的(p后面的字符全可以删除时),所以初始时是dp[s_len][p_len - 1]
class Solution {public: bool isMatch(string s, string p) { /* 数量加1的原因仍然是防止越界 */ std::vector<std::vector<bool> > dp(s.size() + 1, std::vector<bool>(p.size() + 1, false)); /* 上面递归时的边界判断,都到尾端时代表匹配 */ dp[s.size()][p.size()] = true; /* i是从s.size()开始的,原因是dp[s_len][0] ... dp[s_len][p_len - 1]仍然可能是true */ for(int i = s.size(); i >= 0; --i) { for(int j = p.size() - 1; j >= 0; --j) { bool first_match = (i < s.size()) && (s[i] == p[j] || p[j] == '.'); if(j + 1 < p.size() && p[j + 1] == '*') dp[i][j] = (first_match && dp[i + 1][j]) || (dp[i][j + 2]); else dp[i][j] = (first_match && dp[i + 1][j + 1]); } } return dp[0][0]; }};
Wildcard Matching
原题链接Wildcard Matching
这道题和上面的基本一样了,不同点就是’*’的规则有改变,代码如下
没什么特别的,也分递归和非递归,判断的时候多了一个条件,就是’*’可以表示空字符
class Solution {public: bool isMatch(string s, string p) { std::vector<std::vector<bool> > dp(s.size() + 1, std::vector<bool>(p.size() + 1, false)); dp[s.size()][p.size()] = true; for(int i = s.size(); i >= 0; --i) { for(int j = p.size() - 1; j >= 0; --j) { bool first_match = (i < s.size()) && (s[i] == p[j] || p[j] == '?' || p[j] == '*'); /* dp[i][j + 1]是让'*'表示空字符 */ if(p[j] == '*') dp[i][j] = (first_match && dp[i + 1][j]) || (first_match && dp[i + 1][j + 1]) || (dp[i][j + 1]); else dp[i][j] = (first_match && dp[i + 1][j + 1]); } } return dp[0][0]; }/* bool isMatch(string s, string p) { std::vector<std::vector<int> > dp(s.size() + 1, std::vector<int>(p.size() + 1, -1)); return judge_match(0, 0, s, p, dp); }private: int judge_match(int i, int j, const std::string& s, const std::string& p, std::vector<std::vector<int> >& dp) { if(dp[i][j] != -1) return dp[i][j]; if(j >= p.size() && i >= s.size()) return 1; else if(j >= p.size() && i < s.size()) return 0; bool ans; bool first_match = (i < s.size()) && (s[i] == p[j] || p[j] == '?' || p[j] == '*'); if(p[j] == '*') ans = (first_match && judge_match(i + 1, j, s, p, dp) == 1) || (first_match && judge_match(i + 1, j + 1, s, p, dp) == 1) || (judge_match(i, j + 1, s, p, dp) == 1); else ans = (first_match && judge_match(i + 1, j + 1, s, p, dp) == 1); dp[i][j] = ans ? 1 : 0; return dp[i][j]; } */};
- 每天一道LeetCode-----只可能有'.'和'*'的字符串正则匹配
- 每天一道LeetCode-----括号匹配
- 每天一道LeetCode-----字符串乘法
- 每天一道LeetCode-----对表达式添加括号并求值,返回所有可能的计算结果
- 每天一道算法题——字符串匹配
- 每天一道LeetCode-----获取无重复项/有重复项序列的全排列
- 每天一道LeetCode-----将间隔集中有重叠的间隔合并
- 每天一道LeetCode-----给定字符串s和字符数组words,在s中找到words出现的位置,words内部字符串顺序无要求
- 每天一道LeetCode-----将字符串拆分成有效的ip地址
- 每天一道LeetCode-----找到有多少个组合加起来和是n,每个组合的数字只能是1或者2
- 每天一道LeetCode-----找到一个字符串在另一个字符串出现的位置,字符串内部顺序无要求
- [正则表达式]正则表达式(.*)和(.*?)的字符串匹配问题
- 每天一道LeetCode-----在字符串s中找到最短的包含字符串t中所有字符的子串,子串中字符顺序无要求且可以有其他字符
- 有关两个字符串匹配的一道题
- 一道关于字符串匹配的思考题
- 每天一道LeetCode-----某个数在递增序列第一次和最后一次出现的位置
- 每天一道算法题(21)——字符串的全排列和组合算法
- 每天一道LeetCode-----重新实现开方运算sqrt(x),只返回整数部分即可
- 连续总结第八天
- 多态的产生、静态,动态联编
- Secondary NameNode:它究竟有什么作用?
- (未完成)了解LSTM网络(Understanding LSTM Networks)
- Centos配置安装Nginx
- 每天一道LeetCode-----只可能有'.'和'*'的字符串正则匹配
- jdbc的con、pstmt、rs的非正常关闭顺序探讨
- UVA10917 Walk Through the Forest (dijkstra + dfs)
- string
- Unity3D学习入门笔记(三)
- 我的学习记录9
- 【讲解 + 模板】四种最短路算法的比较
- hdu 2767 Proving Equivalences(强连通分量+缩点)
- tensor toolbox 处理稀疏张量