LeetCode第44题----通配符匹配

本题在LeetCode官方中的难度评级为Hard,归属类型为动态规划。


前言

动态规划一直是算法中的难点,难在每道题虽然有大致相同的特点,但是又有独到之处,让人难以寻找其中的规律,也就是说想汇总成一套非常完整的模板基本是不可能的。本文将利用动态规划来解决LeetCode第44题,该题非常之典型,希望可以帮到大家,让我们能从题解中领悟到动态规划的中心思想,找到一套属于自己的解决动态规划的套路。为了方便大家更容易领悟动态规划,可以参考本人的另一篇博文:搞定动态规划----一篇足矣


本人才疏学浅,如有错误请指正。

一、题目描述

截图不易,三联鼓励
示例:
截图不易,三联鼓励
截图不易,三联鼓励

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

二、解题思想和解题步骤

- 分析题目
从题目中我们可以得到的信息有:

  1. ’ ?'可以匹配任意单个字符,但不包括空字符;
  2. ’ * '可以匹配任意字符串,包括空字符串;
  3. 若无1、2两项的特殊情况,就需要 S 和 P 中的字符一一对应且相等,才能得出两字符串相匹配。

- 寻找最优子结构、得出递推方程

状态:

按照动态规划的基本套路,我们定义 dp[ i ][ j ] 数组 , 该数组表示字符串 S 的以 i 结尾的部分与字符串 P 的以 j 结尾的部分是否匹配。

状态转移:

接下来需要考虑 dp 数组如何求得的问题,我们知道字符串 P 可能有三种字符包含在其中,所以我们应该按照如下情况来考虑:

1. P[ j ] 为普通字符,即 P[ j ]为 a ~z 中的一个:
此时判定dp[ i ][ j ]为True 的条件为 dp[ i-1 ][ j-1 ]为True,且P[ j ]与S[ i ]相等,即:dp[ i ][ j ]=dp[ i-1 ][ j-1 ] && P[ j ]==S[ i ];

2. P[ j ]为字符’ ? ':
由于字符’ ? ‘可以匹配任意单个字符,所以不论与之匹配的字符S[ i ]是什么,都是能够匹配成功的,所以此时 dp[ i ][ j ]=dp[ i-1][ j-1 ] && P[ j ]==’ ? ';

3. P[ j ]为字符’ * ':
这是三种情况中最复杂的情况,因为字符’ * '可以匹配任意字符串(包括空字符串),我们无法确定在这种情况下,P[ j ]是用来匹配的字符串长度,即不知道是匹配一个字符还是两个字符,还是更多的字符,所以我们需要分情况进行讨论:

  1. 所匹配的字符串长度为 0 :这种情况就是说 P[ j ]匹配的是空字符,所以可得出 dp[ i ][ j ]=dp[ i ][ j-1 ];

  2. 所匹配的字符串长度为 1 :也就是说P[ j ]只匹配了S[ i ],那么可以得出dp[ i ][ j ]=dp[ i-1 ][ j-1 ];

  3. 所匹配的字符串长度为 2 :这种情况下,P[ j ]匹配了 S[ i ]和S[ i-1 ]两个字符,所以dp[ i ][ j ]=dp[ i-2 ][ j-1 ];
    *
    *
    *
    *
    *
    *
    n. 所匹配的字符串长为n :按照上面的示例,我们很容易就可以得出,dp[ i ][ j ]=dp[ i-n ][ j-1 ];

注意:大家可能不能理解为什么不管匹配的字符串长度为多少,得出的 dp 数组的值都和 dp[ 视情况而定 ][ j-1 ]相等,这是因为字符’ * ‘可以匹配任意长度的字符串,所以可以把 P[ j ]看作是空位,和P[ j ]是’ ? '时的考虑思想一样。

根据上面的推导我们可以知道:当 P[ j ] 是’ * ’ 时,
dp[ i ][ j ]=dp[ i ][ j-1 ] | | dp[ i-1 ][ j-1 ] | | dp[ i-2 ][ j-1 ] | | dp[ i-3 ][ j-1 ] ······ dp[ i-n ][ j-1 ] ;

对于这类问题,我们通常可以通过「代数」进简化,将 i - 1 代入上述的式子得出:

dp[ i−1 ][ j ]=dp[ i−1 ][ j−1 ]∣∣dp[ i−2 ][ j−1 ]∣∣…∣∣dp[ i−k ][ j-1 ] (i>=k)

可以发现,dp[ i - 1 ][ j ] 与 f[ i ][ j ] 中的 dp[ i ][ j - 1 ] 开始的后半部分是一样的,因此有:

dp[ i ][ j ] = dp[ i ][ j - 1 ] | | dp[ i - 1 ][ j ] (i >= 1)

三、代码实现

Python代码:

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        m, n = len(s), len(p)

        dp = [[False] * (n + 1) for _ in range(m + 1)]
        dp[0][0] = True
        for i in range(1, n + 1):
            if p[i - 1] == '*':
                dp[0][i] = True
            else:
                break
        
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if p[j - 1] == '*':
                    dp[i][j] = dp[i][j - 1] 
                    0.| dp[i - 1][j]
                elif p[j - 1] == '?' or s[i - 1] == p[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1]
                
        return dp[m][n]

C++代码:

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];
    }
};

C语言代码:

bool isMatch(char* s, char* p) {
    int m = strlen(s);
    int n = strlen(p);
    int dp[m + 1][n + 1];
    memset(dp, 0, sizeof(dp));
    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];
}

Java代码:

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();
        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;
        for (int i = 1; i <= n; ++i) {
            if (p.charAt(i - 1) == '*') {
                dp[0][i] = true;
            } else {
                break;
            }
        }
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (p.charAt(j - 1) == '*') {
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                } else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
            }
        }
        return dp[m][n];
    }
}

本文代码来源于:文章链接

总结

搞定动态规划,搞定大厂offer!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值