[Leetcode P10]Regular Expression Matching 正则匹配

本文探讨了正则表达式的两种匹配方法:递归解法和动态规划解法。递归解法虽然直观但效率低下;动态规划解法则通过记录子问题的解显著提升了效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总共用了两种解法,一种是我自己递归求解的,效率很低,边界条件很难理清楚,代码很长,第二种是DP解法,把递归的子问题的解存到一个数组里,代码很短。
1. 递归解法
主要想法是根据当前的正则表达式,来决定接下去如何匹配,我们可以用两个字符串的子串去匹配,麻烦的是,可能相同的子字符串被求解了很多遍,特别是.*对应的字符串需要循环求解。写完这个解法以后,我很自然地就知道可以用DP递归求解了,不过边际条件比较难确定,我们可以

struct match
{
    bool isMatch;
    char c;
    int pos;
};

class Solution {
public:

    vector<match> matchArray;
    string reg;

    bool Match(string s, int current){
        for (int i = 0; i < s.length(); ++i)
        {
            if (current >= matchArray.size())
            {
                return false;
            }
            match m = matchArray[current];
            if (m.c == '.' && m.isMatch == true)
            {
                if (current == matchArray.size() - 1)
                {
                    return true;
                }
                else
                {
                    while(matchArray[current].isMatch){
                        current++;
                        if (current >= matchArray.size())
                        {
                            return true;
                        }
                    }
                    char c = matchArray[current].c;
                    bool isMatch = matchArray[current].isMatch;
                    for (int j = i; j < s.length(); ++j)
                    {
                        if (Match(s.substr(j), current))
                        {
                            return true;
                        }
                    }
                    return false;
                }
            }
            else if (m.c == '.')
            {
                current++;
            }
            else if (m.isMatch == true)
            {
                if (Match(s.substr(i), current + 1))
                {
                    return true;
                }
                else if (s[i] != m.c)
                {
                    return false;
                }
                while(s[i] == m.c){
                    i++;
                    if (Match(s.substr(i), current + 1))
                    {
                        return true;
                    }
                }
                return false;
            }
            else if (m.c != s[i])
            {
                return false;
            }
            else{
                current++;
            }
        }
        for (int i = current; i < matchArray.size(); ++i)
        {
            if (matchArray[i].isMatch == false)
            {
                return false;
            }
        }
        return true;
    }

    bool isMatch(string s, string p) {
        reg = p;
        if (p.length() == 0 && s.length() == 0)
        {
            return true;    
        }
        else if (p.length() == 0 /*|| s.length() == 0*/)
        {
            return false;
        }
        for (int i = 0; i < p.length(); ++i)
        {
            match m;
            m.pos = i;
            m.c = p[i];
            if (i + 1 < p.length() && p[i+1] == '*')
            {
                m.isMatch = true;
                i++;
            }
            else{
                m.isMatch = false;
            }
            matchArray.push_back(m);
        }
        return Match(s, 0);
    }
};   
  1. DP解法
    我们可以顺其自然地记录子问题的解,代码里注释比较全。
// 既然可以递归求解,那应该可以记录子节点的情况,做DP
// 我们选择的是p的长度为j的子串是否匹配s的长度为i的子串
class Solution {
public:
    bool dp[100][100];
    bool isMatch(string s, string p) {
        dp[0][0]=true;
        for (int i = 1; i <= s.length(); ++i)
        {
            dp[i][0]=false;
        }
        int len1 = s.length();
        int len2 = p.length();
        for (int i = 0; i <= len1; ++i)
        {
            // 注意i=0对应的是s是空串的情况
            for (int j = 1; j <= len2; ++j)
            {
                // 同样的j=0对应的是p=0的情况,而且我们已经初始化过了
                // i,j对应的是字符串的
                if (p[j-1] == '*')
                {
                    if (p[j-2]=='.' || (i>0&&p[j-2]==s[i-1]))
                    {
                        // p_j-2可以匹配s的结尾,那么只需要匹配情况和长度为i-1的s一样就可以了
                        // 主要是x*可以匹配0/1个:
                        // 1 如果p可以匹配s,则px*一定匹配sx
                        // 2 如果p不可以匹配s,则:
                        //     2.1 如果希望px*匹配sx,那么说明x*匹配的不是单一的x:
                        //          2.1.1 x*匹配的是空,则p匹配sx也就是dp[i][j-2]=true
                        //          2.1.2 x*匹配的是xxxxx,做了k次匹配,则px*一定也可以匹配sxxxx(k-1个x)
                        //                ,也就是dp[i-1][j]=true(还要求i>0)
                        //     2.2 匹配的情况只有以上两种,不匹配的话取反就可以了,而dp[i][j-2]已经做了相应的记录,直接用就好
                        // 而且因为*不会出现在p开头,所以j>=2
                        // 这里可能会出现一些误解,只要想清楚p[j-2]对应的长度是j-1,dp[i][j-2]对应的长度是j-2就可以
                        dp[i][j]=(i>0&&dp[i-1][j])||dp[i][j-2];
                    }
                    else{
                        // 如果不能匹配结尾,说明x*毫无作用,它做了0次匹配,对应的是2.1.1的情况
                        dp[i][j]=dp[i][j-2];
                    }
                }
                else if (i>0 && (p[j-1]=='.' || p[j-1]==s[i-1]))
                {
                    // 这里如果p可以匹配s,那么p.可以匹配sx,如果p不可以匹配s,那么p.也不可以匹配sx
                    // px和sx是同理的
                    dp[i][j]=dp[i-1][j-1];
                }
                else{
                    // 其他情况下,也就是说,pa不匹配sb,那么我们暂时让它等于false
                    // 这个条件,我在yy的时候,一直在想p有.*的情况,好像理不清楚的样子
                    // 其实很简单,p'.*a和sb一样是不匹配的,p'.*其实在第一步讨论过了
                    dp[i][j]=false;
                }
            }
        }
        return dp[len1][len2];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值