• Leetcode:正则表达式匹配


    目录

    普通版本(动态规划)

    状态表示

    状态转移方程

    优化③①情况

    数学化简分析

    结合实际情况画图化简分析

    总结

    最终代码


    题目链接:10. 正则表达式匹配 - 力扣(LeetCode)

    好像是leetcode前100道里面最难的一道?我是fw🤡

    普通版本(动态规划

    题目条件:

    • '.' 匹配任意单个字符
    • '*' 匹配零个或多个前面的那一个元素(将其理解为*号,某字符*表示可以有n个该字符)
    • 每次出现字符 * 时,前面都匹配到有效的字符(只有a~z*、.*两种情况)

    注意事项:字符*,变化的多出来的字符是向后延申的

    条件解析与示例分析:

    示例1:s = "aa"、p = "a",可匹配

    示例2:s = "aa"、p = "a*",a*是一个整体,可以表示0~n个a,可匹配

    示例3:s = "ab"、p = ".*",.*是一个整体,可以表示0~n个.,每个.可代表任意字符,可匹配

    示例4:s = "aab"、p = "d*a*b",将d*视为一个空串,a*表示两个a,b不变,可匹配

    状态表示

    分析:由题意得我们可以要判断的是p中[0,j]范围内的字符是否能匹配s中[0,i]范围内的字符,涉及了两个数组间情况得判断

    结论:状态转移方程为dp[i][j],该方程表示p[0,j]范围内的子串是否可以匹配s[0,j]范围内的子串 

    状态转移方程

    主旨:根据p数组最后位置的取值进行分情况讨论(s[i]和p[j]分别表示s和p数组最后位置的字符)

    ①p[j]是a~z的字符

    满足  p[j] == s[i] && dp[i-1][j-1] == true 时dp[i][j]为真(当前和之前所有位置都为真)

    ②p[j]是字符.

    满足dp[i-1][j-1]==true,则dp[i][j]为真(只要之前均为真,.必可使最后一个位置也相同)

    ③p[j]是* 

    ③①p[j-1]是.

    ③①①.*表示空串

    • 此时p[j-1]和p[j]位置报废,若dp[i][j-2]为真则dp[i][j]为真,即p的[0,j-2]范围内的字符能否与s的[0,i]范围内的字符匹配

    ③①②.*表示一个.

    • 此时p[j]位置报废p[j-1]位置为.若dp[i-1][j-2]为真则dp[i][j]为真,即p的[0,j-2]范围内的字符能否与s的[0,i-1]范围内的字符匹配,因为p[j-1]位置的.能表示任意字符,故s[i-1]位置不论是什么字符dp[i-1][j-1]一定为真

    ③①③.*表示两个.

    • 此时p[j]和p[j-1]位置均为.若dp[i-2][j-2]为真则dp[i][j]为真,即p的[0,j-2]范围内的字符能否与s的[0,i-2]范围内的字符匹配,因为p[j]和p[j-1]位置的.能表示任意字符,故s[i]h和s[i-1]位置不论是什么字符dp[i][j]和dp[i-1][j-1]一定为真

    ③①④.*表示三个.

    • 此时p[j]和p[j-1]均为甚至p[j+1]位置均为.若dp[i-3][j-2]为真则dp[i][j]为真,即p的[0,j-2]范围内的字符能否与s的[0,i-3]范围内的字符匹配...(都一样不重复解释了)

    p[j-1]是a~z的字符

    ②①字符*表示空串

    • 此时p[j-1]和p[j]位置报废,若dp[i][j-2]为真则dp[i][j]为真(与上面一样了,不解释了)

    ③②②判断j-1位置的字符是否与i位置相同(p[j-1] == s[i])

    • 相同则保留字符*的状态,此时若dp[i-1][j]为真则dp[i][j]为真因为s[j-1]位置上的字符与i位置匹配后因为可以用*造出很多个该字符,所以只需考虑p[0,j]范围内的字符能否与s[0,i-1]范围内的字符匹配即可(而判断这一个范围内的情况,就需要再次到③②②这种情况来进行判断),如果均匹配则让*造字符时多加一个该字符即可

    优化③①情况

    优化结果:当p[j]为*、p[j-1]为.时,dp[i][j] = dp[i][j-2] || dp[i-1][j]

    初步分析:由③①中的多条结论可以得出,当满足其中一个结论时,dp[i][j]就为真,因为此时p[j-1]和p[j]的组合为.*,如果dp[i][j-2]为真,那么.*只需要创建出一个.即可,如果dp[i-3][j-2]为真,那么.*只需要创建出三个.即可,即只要前面的相等,后面的.*就绝对可以保证后面的所有都相等,即:①p[j] == '*'时,dp[i][j] = dp[i][j-2] ||  dp[i-1][j-2] || dp[i-2][j-2] ...,但是这个式子还是有点长,那么是否能继续化简一下?

    数学化简分析

    我们令i=i-1,重新套入上面的式子可得:

    ②p[j] == '*'时,dp[i-1][j] = dp[i-1][j-2] ||  dp[i-2][j-2] || dp[i-3][j-2] ...

    我们发现,满足dp[i-1][j]为真需要满足dp[i-1][j-2] ||  dp[i-2][j-2] || dp[i-3][j-2] ...的某个条件为真,而该条件与①中的后半部分完全相同,由简单的数学等价替换就可以得到我们优化③①情况的优化结果

    结合实际情况画图化简分析

    当p[j]为*、p[j-1]为.时,我们可以分为两种情况:

    情况一:当.*组合与s[i]相同后后不变化仍然保留.*组合,接着比较s[i-1],如果dp[i-1][j]为真则dp[i][j]为真(s的[0,i-1]范围内的字符与p[0,j]范围内的字符可以匹配)

    情况二:将.*视为一个空串,那么此时如果dp[i][j-2]为真,则dp[i][j]也就为真

    总结

    1、①和②可以总结为一种情况:当p[j]==s[i] 或者 p[j]==. 为真 且dp[i-1][j-1]为真(两个的末尾都匹配了 或者 p的末尾为.时无论s的末尾为什么字符都能匹配),那么dp[i][j]就为真

    2、③可以总结为:当 dp[i][j-2] 为真或者 (p[j-1] == '.' || p[j-1] == s[i])一个为真且 dp[i-1][j] 为真,则dp[i][j]为真(整合后p[j]==*时引发的所有结论,可以发现这些结论的本质就是围绕p[j-1]*的组合是空串还是非空串进行研究并得出结论的)

    • p[j-1]*的组合为空:由上图的分析得,当p[j]为*时一定有“某字符*”是空串的情况,故可以将③①和③②为空的情况合并得到判断dp[i][j]是否为空的一个条件dp[i][j-2]
    • p[j-1]*的组合不为空:当p[j-1]为.时该组合为“.*”,此时只要dp[i-1][j]为真即可(上面优化里的当dp[j]为*、p[j-1]为.时dp[i][j]为真的两个条件dp[i][j-2] || dp[i-1][j],p[j-1]*组合为空时候占用了一个dp[i][j-2],现在就剩下另一个条件了,当然如果你不信也可以再推一遍),当p[j-1]为a~z的字符时该组合为“a~z的字符*”,此时也是只需要dp[i-1][j]为真即可

    最终代码

    1. class Solution {
    2. public:
    3. bool isMatch(string s, string p) {
    4. int m = s.size(),n = p.size();//二者大小不一样相同
    5. //1、创建dp表
    6. vectorbool>> dp(m+1,vector<bool>(n+1));//创建一个m行n列的dp表,+1是为了创建辅助行
    7. //2、初始化
    8. s = ' ' + s,p = ' ' + p;//加上一行辅助位,此时字符串下标和dp表下标一致,不需要在填表时做下标-1的操作
    9. //先填写左上角的辅助行为true,处理特殊情况,即当s为空时,根据p中字符*的情况得到匹配结果(准备工作)
    10. dp[0][0] =true;
    11. //更新dp表
    12. for(int j = 2;j<=n;j+=2)
    13. if(p[j]=='*') dp[0][j] =true;
    14. else break;
    15. //3、填表
    16. for(int i = 1;i <= m;i++)
    17. {
    18. for(int j = 1;j <= n;j++)
    19. {
    20. //p[j]为*L:③的所有情况得到的结论
    21. if(p[j] == '*')
    22. {
    23. dp[i][j] = dp[i][j-2] || (p[j-1] == '.' || p[j-1] == s[i]) && dp[i-1][j];
    24. }
    25. //p[j]不为*:①②两种情况得到的结论
    26. else
    27. {
    28. dp[i][j] = (p[j] == s[i] || p[j] == '.')&&dp[i-1][j-1];
    29. }
    30. }
    31. }
    32. //4、返回值
    33. return dp[m][n];//题目要求的是完全匹配,所以判断范围为[0,m]和[0,n];
    34. }
    35. };

    自我总结:动规主要的内容就是确认状态表示和状态转移方程,由特例推广至全部,只需要直到宏观规律即可

    ~over~

  • 相关阅读:
    【洛谷 P8682】[蓝桥杯 2019 省 B] 等差数列 题解(数学+排序+差分)
    基于功能安全的车载计算平台开发:系统层面
    Vue3+Elementplus引入面包屑功能
    MyEclipse 2017 安装与pj
    10.1 调试事件读取寄存器
    Shell脚本 & Sed流编辑器 & awk语法
    vue3.0项目实战系列文章 - 登录页面
    Rust核心功能之一(所有权)
    ARM 实例代码
    系统性能优化-HL
  • 原文地址:https://blog.csdn.net/m0_73975164/article/details/139388486