模式串匹配

文章介绍了使用动态规划解决模式串匹配问题的方法,探讨了动态规划、回溯和递归的概念,并详细解释了在处理字符串匹配时如何利用状态转移数组dp进行计算。匹配规则涉及*和?字符的特殊处理,并提供了相关代码示例。

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

模式串匹配方式:

        在模式串与字符串的匹配中,我们处理匹配这类的问题一般都是动态规划。但是,我不知道读者是否知道为什么选用动态规划?这要从动态规划的特性说起来。

        动态规划:动态规划是记录每一个可达状态的情况,从而化大为小。同时,可达状态也表示

        了状态是可转移的,可记录(可被设计)的

        回溯:强调每一个状态的可回退性,也就是我们发现情况不对,可以从恰当的“起点”重新来

        过。

        递归:从严格意义上,递归只是指函数调用函数本身。但是递归确实是实现“回溯算法”的重要

        手段。但是可以记忆化搜索的递归,也是一种动态规划,所以动态规划也不应该限制于递

        推。

这些概念相当接近,因为它们在大部分情况下是有联系的。所以动态规划不能只是单纯的空间换时间!因为这不是它的特性,空间换时间是大部分优化时间复杂度的算法特性。

而在模式串匹配中,我们通常都想着,如果两个字符串中有两个字母匹对成功,那么加上新字母的子串是否匹配成功,取决于老状态是否匹配。

新状态依赖老状态,或者说新状态可由老状态转移而来。所以我们只需要记录每一个匹配状态就好。

匹配规则1:

  • '?' 可以匹配任何单个字符。
  • '*' 可以匹配任意字符序列(包括空字符序列)。

 (声明:题目取自leetcode)

现在我们就知道我们需要去记录一个匹配状态。

所以我们可以设计一个用记录状态的数组dp(可称记忆数组),含义为s[0,i-1]于p[0,j-1]的匹配情况;

int m = s.size();

int n = p.size();

vector<vector<int>> dp(m + 1, vector<int>(n + 1));

接下来我们先考虑状态的转移:

1.如果是p[j] == '?' || p[j] == s[i] 那么 dp[i][j] = dp[i-1][j-1];

   解释:也就取决于加入匹配字符前的状态 

2.如果 p[j] == '*' 那么 dp[i][j] = dp[i][j-1] | dp[i-1][j];

   解释:如果是‘*’,那么有两种情况。(1)‘*’匹配空; (2)‘*’匹配1个或多个字符;

  当匹配空时,也就是说明看 dp[i][j-1]的记录;如果非空,就看dp[i-1][j];

  但是不容易理解的是为什么是dp[i-1][j],而不是dp[i-1][j-1];

  答案在于多个匹配。从含义上说[j-1]将‘*’剔除,也就是说‘*’只匹配一次。而[j]是包含‘*’意味着可以    匹配多次。但是从表达式上说,匹配一个是dp[i-1][j-1] 两个是看dp[i-2][j-1] 匹配k个 dp[i-k][j-1];

  我们会发现匹配发生在子串末尾,并且每次匹配都是短子串先行!所以当我们记录新子串匹配情    况时,dp[i][j] = dp[i-1][j]; dp[i-1][j] = dp[i-2][j];...;dp[i-k+1][j] = dp[i-k][j];因为‘*’匹配k个字符。所以     dp[i-k][j]是空匹配的状态也就是  dp[i-k][j] = dp[i-k][j-1];

        for (int i = 1; i <= m; ++i) {

            for (int j = 1; j <= n; ++j) {

                if (p[j - 1] == '*') {

                    dp[i][j] = dp[i][j - 1] | dp[i - 1][j];

                }

                else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) {

                    dp[i][j] = dp[i - 1][j - 1];

                }

            }

        }

接下来是初始状态设计,我们知道。空字串只能跟空子串匹配,或者跟‘*’匹配。但是这个‘*’只能是开头。否则就不是从头至尾的匹配,而是可以从中间匹配;具体原因就是dp[0]的作用不仅仅是初始化,还会在匹配中起到切割作用。例如dp[0][j] = 1;意味着,s可以从p模式串的第j位开始匹配,最本质的意思是现在p[0-j]视为空集合

for (int i = 1; i <= n; ++i) {

            if (p[i - 1] == '*') {

                dp[0][i] = true;

            }

            else {

                break;

            }

        }

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size();
        int n = p.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        dp[0][0] = true;
        for (int i = 1; i <= n; ++i) {
            if (p[i - 1] == '*') {
                dp[0][i] = true;
            }
            else {
                break;
            }
        }
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (p[j - 1] == '*') {
                    dp[i][j] = dp[i][j - 1] | dp[i - 1][j];
                }
                else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
            }
        }
        return dp[m][n];
    }
};

如果去掉初始化的退出部分可以试试以下测试样例:

s = “ab” p = "a*ab";

匹配规则2:

 

 (声明:题目取自leetcode)

这个道题跟之前一样,但是不同的是,现在‘*’只能匹配多个一种字符。也就是所会有一种字符跟“*”捆绑成一体。

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size();
        int n = p.size();
        vector<vector<bool> > f(m+1, vector<bool>(n+1, false));
        f[0][0] = true;
        //i = 0 时初始化也是从前往后初始化防止从中间开始匹配 但是允许跳过星号,注意题目意思“c*” 
        //是捆绑的整体,即该*只匹配任意个c字符可以为0。
        for(int i = 0; i <= m; ++i)
        {
            for(int j = 1; j <= n; ++j)
            {
                if(p[j-1] == '*')
                {
                    f[i][j] = f[i][j] | f[i][j-2];
                    if(i == 0) continue;
                    if(p[j-2] == '.' || p[j-2] == s[i-1])
                    {
                        f[i][j] = f[i][j] | f[i-1][j];
                    }
                }
                else
                {
                    if(i == 0) continue;
                    if(p[j-1] == '.' || p[j-1] == s[i-1])
                    {
                        f[i][j] = f[i][j] | f[i-1][j-1];
                    }
                }
            }
        }

        return f[m][n];
    }
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值