leetcode
leetcode 2551 ~ 2600
模糊搜索验证

模糊搜索验证

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 48 ms, 内存: 15 MB


/*
 * 思路:
 * 虽然 Java Stream 并不直接适用于这种动态规划问题,但我们可以使用 Stream 来简化一些数据处理的步骤。
 * 因此,解决方案还是基于动态规划,只是部分操作可以使用 Stream 来完成。
 */
import java.util.stream.IntStream;

public class RegexMatchingStream {
    public boolean isMatch(String article, String input) {
        int m = article.length();
        int n = input.length();
        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;
        IntStream.range(1, n + 1).forEach(j -> {
            if (input.charAt(j - 1) == '*') {
                dp[0][j] = dp[0][j - 2];
            }
        });
        IntStream.range(1, m + 1).forEach(i -> {
            IntStream.range(1, n + 1).forEach(j -> {
                if (input.charAt(j - 1) == '.' || input.charAt(j - 1) == article.charAt(i - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else if (input.charAt(j - 1) == '*') {
                    dp[i][j] = dp[i][j - 2];
                    if (input.charAt(j - 2) == '.' || input.charAt(j - 2) == article.charAt(i - 1)) {
                        dp[i][j] = dp[i][j] || dp[i - 1][j];
                    }
                }
            });
        });
        return dp[m][n];
    }
}

解释

方法:

此题解采用了动态规划(DP)的方法来解决正则表达式匹配问题。解决方案通过递归函数 dp(i, j) 检查原始字符串 s 从位置 i 开始的子串是否可以被模式 p 从位置 j 开始的子串匹配。使用 memoization 技术(memo 字典)来避免重复计算相同参数的函数调用,从而提高效率。对于基本情况,当模式 p 已经全部匹配完毕 (j == n),检查 s 是否也完全匹配 (i == m)。若 p 未结束但 s 已结束,则检查剩余的 p 是否可以匹配空字符串。递归地处理模式中的特殊字符 '*' 和 '.',其中 '.' 可以匹配任意单个字符,'*' 表示前一个字符可以出现零次或多次。

时间复杂度:

O(m*n)

空间复杂度:

O(m*n)

代码细节讲解

🦆
动态规划算法中,你是如何确定初始条件以及状态转移方程的?
在此动态规划解法中,初始条件基于字符串 `s` 和 `p` 的结束情况来定义。如果模式 `p` 已经检查完毕(即 `j == n`),则必须检查字符串 `s` 是否也已经结束 (`i == m`),这是基础匹配条件。状态转移方程基于当前字符和模式的匹配情况而定,特别是考虑到模式字符 '.' 和 '*' 的处理。'.' 匹配任意单个字符,而 '*' 表示其前一个字符可以出现零次或多次,这导致多个分支的递归调用,形成了状态转移的逻辑。
🦆
在处理字符'*'时,`dp(i, j+2)` 和 `dp(i+1, j)` 分别代表什么含义,为什么需要两种处理方式?
`dp(i, j+2)` 代表在模式 `p` 中跳过当前的字符和其后的 '*',这反映了 '*' 的零次出现场景。`dp(i+1, j)` 表示当前字符在 `s` 中匹配后,继续在 `p` 中使用相同的模式进行匹配,这反映了 '*' 的多次出现场景。这两种处理方式是必需的,因为 '*' 的语义允许前一个字符出现任意次,从零次到多次。
🦆
递归函数中,当原文字符串 `s` 已经结束但模式字符串 `p` 未结束时,为什么要检查 `(n - j) % 2 == 1` 这个条件?
当 `s` 结束但 `p` 未结束时,我们需要检查 `p` 剩余部分是否能匹配空串。`'(n - j) % 2 == 1'` 检查是因为,如果 `p` 中剩余字符数为奇数,那么这些字符不能完全由 '字符+*' 的组合构成(因为每个这样的组合都占两个位置),从而无法匹配空串。该检查帮助确定是否有非法的单独字符存在,这些字符无法通过 '*' 来抵消其影响。
🦆
为什么在模式字符串 `p` 中连续的'*'字符后面要紧接着检查 `while j + 1 < n and p[j+1] == '*'`,这个循环的目的是什么?
这个循环的目的是处理模式中的冗余 '*' 字符。在模式匹配中,如果一个字符后面直接跟着多个 '*',这是语法上的冗余,因为一个 '*' 已经足够表示其前字符的零次或多次出现。这个循环通过跳过多余的 '*',简化模式字符串,确保每个字符后面最多只跟一个 '*',从而避免解析上的混乱和潜在的错误处理。

相关问题