力扣题目-通配符匹配

98 阅读5分钟

一、题目

给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配:

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

判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

 

示例 1:

输入: s = "aa", p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入: s = "aa", p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

示例 3:

输入: s = "cb", p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'

 

提示:

  • 0 <= s.length, p.length <= 2000
  • s 仅由小写英文字母组成
  • p 仅由小写英文字母、'?' 或 '*' 组成

二、解答

题目分析

本题要求实现一个通配符匹配算法,给定输入字符串 s 和字符模式 p,需判断 p 是否能完全匹配 s。其中,? 可匹配任意单个字符,* 能匹配任意字符序列(包含空字符序列)。匹配必须是完全匹配,而非部分匹配。

示例分析

  • 示例 1

    • 输入:s = "aa"p = "a"
    • 输出:false
    • 解释:模式 "a" 只能匹配一个字符 a,而输入字符串 "aa" 有两个字符 a,所以模式无法匹配整个字符串。
  • 示例 2

    • 输入:s = "aa"p = "*"
    • 输出:true
    • 解释:模式中的 * 可匹配任意字符序列,因此能匹配输入字符串 "aa"
  • 示例 3

    • 输入:s = "cb"p = "?a"
    • 输出:false
    • 解释:模式中的 ? 可匹配字符串中的 c,但模式中的 a 无法匹配字符串中的 b,所以匹配失败。

代码(Java)

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

代码分析

整体功能概述

这段代码实现了一个支持 ? 和 * 通配符匹配的算法,用于判断给定的字符串 s 是否能被模式字符串 p 完全匹配。? 可以匹配任意单个字符,* 可以匹配任意字符序列(包括空字符序列)。代码采用动态规划的方法来解决这个问题。

代码结构和变量定义

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;
  • m 和 n 分别存储字符串 s 和模式字符串 p 的长度。
  • dp 是一个二维布尔数组,dp[i][j] 表示字符串 s 的前 i 个字符和模式字符串 p 的前 j 个字符是否匹配。dp 数组的大小为 (m + 1) x (n + 1),这样可以方便处理空字符串的情况。
  • dp[0][0] 初始化为 true,因为空字符串和空模式是匹配的。

初始化 dp 数组的第一行

java

for (int i = 1; i <= n; ++i) {
    if (p.charAt(i - 1) == '*') {
        dp[0][i] = true;
    } else {
        break;
    }
}
  • 这部分代码用于初始化 dp 数组的第一行。当字符串 s 为空时,如果模式字符串 p 以连续的 * 开头,那么这些 * 可以匹配空字符串,所以对应的 dp[0][i] 为 true。一旦遇到非 * 字符,就停止初始化,因为非 * 字符无法匹配空字符串。

填充 dp 数组

java

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];
        }
    }
}
  • 这是两层嵌套的循环,用于填充 dp 数组的其余部分。

    • 当 p.charAt(j - 1) == '*' 时:

      • dp[i][j - 1] 表示 * 匹配空字符串,即忽略当前的 *,看 s 的前 i 个字符和 p 的前 j - 1 个字符是否匹配。
      • dp[i - 1][j] 表示 * 匹配 s 的当前字符,即 * 继续匹配更多的字符,看 s 的前 i - 1 个字符和 p 的前 j 个字符是否匹配。
      • 只要这两种情况中有一个为 true,则 dp[i][j] 为 true
    • 当 p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1) 时:

      • 表示当前字符匹配,那么 dp[i][j] 的值取决于 dp[i - 1][j - 1],即 s 的前 i - 1 个字符和 p 的前 j - 1 个字符是否匹配。

返回结果

java

return dp[m][n];

  • 最终返回 dp[m][n],表示字符串 s 的全部字符和模式字符串 p 的全部字符是否匹配。

总结

问题核心

该 Java 代码要解决的核心问题是实现一个支持 ? 和 * 通配符匹配的算法,判断给定字符串 s 能否被模式字符串 p 完全匹配。其中,? 可匹配任意单个字符,* 能匹配任意字符序列(包含空字符序列)。

实现思路

采用动态规划的方法来解决此问题。通过创建一个二维布尔数组 dpdp[i][j] 表示字符串 s 的前 i 个字符和模式字符串 p 的前 j 个字符是否匹配。

代码步骤
  1. 变量和数组初始化:获取字符串 s 和模式字符串 p 的长度 m 和 n,创建 (m + 1) x (n + 1) 的二维布尔数组 dp,并将 dp[0][0] 初始化为 true,表示空字符串和空模式匹配。

  2. 初始化 dp 数组第一行:当字符串 s 为空时,若模式字符串 p 以连续的 * 开头,对应 dp[0][i] 为 true,遇到非 * 字符则停止初始化。

  3. 填充 dp 数组:使用两层嵌套循环遍历 s 和 p 的每个字符,根据不同情况更新 dp[i][j] 的值。

    • 当 p 中当前字符为 * 时,dp[i][j] 取决于 dp[i][j - 1]* 匹配空字符串)或 dp[i - 1][j]* 匹配 s 当前字符)。
    • 当 p 中当前字符为 ? 或与 s 中当前字符相同时,dp[i][j] 取决于 dp[i - 1][j - 1]
  4. 返回结果:返回 dp[m][n],表示 s 和 p 是否完全匹配。

复杂度情况
  • 时间复杂度:O(m∗n),因为需要填充一个大小为 (m+1)x(n+1) 的二维数组,其中 m 是字符串 s 的长度,n 是模式字符串 p 的长度。
  • 空间复杂度:O(m∗n),主要用于存储 dp 数组。