使用动态编程理解正则表达式字符串匹配

2024-05-04

我遇到了这个问题,要求您实现一个支持“.”的正则表达式匹配器。和“*”,其中

'.'匹配任何单个字符。

'*' 匹配零个或多个前面的元素。

isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true

虽然我能够以线性方式解决这个问题,但我遇到了很多使用 DP 的解决方案,如下所示,

class Solution {
    public boolean isMatch(String text, String pattern) {
        boolean[][] dp = new boolean[text.length() + 1][pattern.length() + 1];
        dp[text.length()][pattern.length()] = true;

        for (int i = text.length(); i >= 0; i--){
            for (int j = pattern.length() - 1; j >= 0; j--){
                boolean first_match = (i < text.length() && 
                                       (pattern.charAt(j) == text.charAt(i) ||
                                        pattern.charAt(j) == '.'));
                if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
                    dp[i][j] = dp[i][j+2] || first_match && dp[i+1][j];
                } else {
                    dp[i][j] = first_match && dp[i+1][j+1];
                }
            }
        }
        return dp[0][0];
    }
}

我很难理解这一点。我已经解决了一些涉及网格的 DP 问题(二维网格中的最短路径,二进制二维网格中的最大正方形),使用 DP 表对我来说非常有意义。然而在这里我完全迷失了,我无法理解遍历二维表如何帮助解决这个问题。此外,我们似乎知道循环中的字符何时不匹配,所以我不明白为什么我们不在那里终止搜索(这也可能是由于我不了解表遍历如何导致一个办法)。对于此类问题有清晰直观的解释吗?


使用 DP 解决此类问题的直觉是找出以下问题的答案

  1. 问题可以用递归来解决吗?这意味着它可以用同类型的较小子问题来表示?
  2. 较小的子问题是否会在递归树中重复?如果是,可以将较小问题的结果存储为每当遇到类似的子问题时都可以在 O(1) 中访问结果的方式。这通常称为记忆化。

让我们首先了解您在以线性方式求解时可能找到的问题的解决方案。

  1. 将文本与模式匹配时,第一个字符要么匹配,要么不匹配。

    情况 1:第一个字符匹配或模式的第一个字符是“.”

    案例 1.1 下一个字符是 '*'

    情况 1.2 下一个字符不是 '*'

    情况 2:第一个字符不匹配

    案例 2.1 下一个字符是 '*'

    case 2.2 下一个字符不是 '*'

现在让我们找出前面讨论的解决上述问题的两个步骤。

对于情况 1.1 的例子是

isMatch("a", "a*a") and isMatch("aab", "a*b")这相当于解决

isMatch("a", "a") || isMatch("", "a*a") and

isMatch("aab", "b") || isMatch("ab", "a*b")分别。第一部分||条件涵盖了模式中可选字符的场景,因为它后面跟着“*”,第二部分涵盖了匹配字符重复的场景。

类似地,可以为所有情况形成子问题。情况2.2应该 直接返回 false。

下面是使用递归方法的java代码

public boolean isMatch(String text, String pattern) {
    dp = new Boolean[text.length()][pattern.length()];
    return isMatch(text, pattern, 0, 0);
}

private boolean isMatch(String text, String pattern, int ti, int pi) {

    if (pi == pattern.length() && ti < text.length()) return false;

    if (ti == text.length() && pi == pattern.length()) return true;

    if (ti == text.length()) {
        return isNextCharStar(pattern, pi) && isMatch(text, pattern, ti, pi + 2);
    }


    boolean result;
    final boolean hasFirstMatched = text.charAt(ti) == pattern.charAt(pi) || pattern.charAt(pi) == '.';

    if (isNextCharStar(pattern, pi)) {
        result = isMatch(text, pattern, ti, pi + 2);
        if (hasFirstMatched) {
            result = result || isMatch(text, pattern, ti + 1, pi);
        }
        return result;
    }

    return hasFirstMatched && isMatch(text, pattern, ti + 1, pi + 1);

}

private boolean isNextCharStar(String pattern, int pi) {
    return pi < pattern.length() - 1 && pattern.charAt(pi + 1) == '*';
}

现在让我们应用记忆步骤。如果我们在返回之前开始保存结果,我们就可以在下次重用它。我们应该如何以及在哪里保存它? 对于所有可能的组合ti and pi我们必须存储结果。在哪里ti是文本索引并且pi是模式索引。所以一个大小的二维数组text.length * pattern.length应该足够了。以下是上述更改后的代码

Boolean [][] dp;

public boolean isMatch(String text, String pattern) {
    dp = new Boolean[text.length()][pattern.length()];
    return isMatch(text, pattern, 0, 0);
}

private boolean isMatch(String text, String pattern, int ti, int pi) {

    if (pi == pattern.length() ) return ti == text.length();

    if (ti == text.length()) {
        return isNextCharStar(pattern, pi) && isMatch(text, pattern, ti, pi + 2);
    }

    if(dp[ti][pi] != null) return dp[ti][pi];

    boolean result;
    final boolean hasFirstMatched = text.charAt(ti) == pattern.charAt(pi) || pattern.charAt(pi) == '.';

    if (isNextCharStar(pattern, pi)) {
        result = isMatch(text, pattern, ti, pi + 2);
        if (hasFirstMatched) {
            result = result || isMatch(text, pattern, ti + 1, pi);
        }
        dp[ti][pi] = result;
        return result;
    }

    dp[ti][pi] = hasFirstMatched && isMatch(text, pattern, ti + 1, pi + 1);
    return dp[ti][pi];

}

private boolean isNextCharStar(String pattern, int pi) {
    return pi < pattern.length() - 1 && pattern.charAt(pi + 1) == '*';
}

如果您仔细观察,只更改了 3 行,使其从递归解决方案变为 DP 解决方案。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用动态编程理解正则表达式字符串匹配 的相关文章

  • 将 hyperjaxb3 升级到 jpa 2.1

    我正在尝试在使用 maven jpa hibernate 和 hyperjaxb 的 eclipse 项目中升级到 JPA 2 1 当我尝试执行以下操作时出现以下错误Run As Run on Server从日食内部 java lang N
  • 传输级别信息与 SOAP 消息命名空间 URI 不匹配

    我收到错误 Transport level information does not match with SOAP Message namespace URI 要求您提供详细信息以解决问题 我在客户端设置了以下内容 HttpTranspo
  • 使用 Jquery Ajax 将数据从 jsp 发送到 struts2 操作类

    我需要使用 jquery Ajax 将表单数据从 jsp 传递到 struts2 并从 Struts2 操作类接收回 JSON 数据 我已经给出了下面的代码 当我传递 AJAX 数据时 url search action searchTex
  • 压缩很多小字符串的算法?

    我正在寻找一种压缩小 ASCII 字符串的算法 它们包含大量字母 但也可以包含数字和很少的特殊字符 它们很小 平均约为 50 100 字节 最多 250 个字节 例子 Android show EditText setError above
  • 动态更新 LookAndFeel 值

    我希望能够动态更新 Swing GUI 的 LookAndFeel 属性 在本例中 我有一个简单的 Swing Awt 游戏 运行最初为 Nimbus 的游戏LookAndFeel 在启动后的各个时刻 我只想更改 比方说 一个细节 应用程序
  • “传输协议线程失败” – “套接字为 EOF”,使用 Java 进行 J2SSH 连接

    我正在尝试通过我的 Java 代码建立 SSH 连接 但遇到异常 我通过 Putty Winscp 工具测试了我的连接 它工作正常 问题出在我的 Java 代码上 SEVERE The Transport Protocol thread f
  • 如何在Java中实现复合模式?

    我想实现一个复合模式Java以便绘制软件开发组织图 因此 我们假设有多个项目经理和多个开发人员 每个开发人员都被分配给一位项目经理 并且每个开发人员都能够使用各种编程语言进行编码 项目经理领导开发人员并准确了解他们的工作量 我对这个设计模式
  • Spring Boot 1.4:Liquibase完成后的执行方法

    我有一个基于 Spring Boot 1 4 0 的项目 该项目使用 Liquibase liquibase 完成后是否可以执行方法 像 Bean 后处理器之类的东西 我想要做的是当应用程序在开发模式下启动时向我的数据库添加一些数据 在开发
  • 我可以直接在 Maven 中使用 GitHub 项目吗?

    我有兴趣使用GitHub 上的项目 https github com toelen spymemcached jcache作为我的项目中的依赖项 GitHub 项目有一个pom文件 我可以修改我的pom文件来使用这个项目 如果是这样 怎么办
  • Windows:如何获取所有可见窗口的列表?

    无论如何都要使用相关技术重新标记 我不知道它们是什么 稍后我可能会提出更详细的问题 关于具体细节 但现在我正在尝试掌握 大局 我正在寻找一种方法来枚举 Windows 上的 真实可见窗口 我所说的 真正可见的窗口 就是指 用户所说的 窗口
  • 优化两个三位数乘积的最大回文数?

    我正在研究一个面试问题 我被问到这个问题 我应该编写一个程序 从两个三位数的乘积中找到最大的回文数 这里是question https projecteuler net problem 4 我想出了这种从底部开始的蛮力方法 public c
  • 从 Java/Spring 检索 RabbitMQ 队列中未确认消息的数量

    有没有办法返回未确认的消息数 我正在使用此代码来获取队列中的消息数 DeclareOk declareOk amqpAdmin getRabbitTemplate execute new ChannelCallback
  • 部署到 Glassfish 4.1 时 URL 模式无效

    如果用户已经通过身份验证 我有一个网络过滤器可以从登录和索引页面重定向 最初我有一个无效的 URL 模式 我修复了无效模式并尝试重新部署以接收以下内容 java lang IllegalArgumentException Invalid U
  • DOM 中不再存在缓存元素

    就像在类似的问题中一样 我使用appium java 尝试选择元素 在移动应用程序中 我要转到页面 之后有许多元素 android widget ImageView 0 我需要选择 6 个 例如 这样的元素并执行其他步骤 Byt 只能选择一
  • 如何使用 Java 1.4 和 SAX 将任意数据编码为 XML?

    我们使用 SAX 来解析 XML 因为它不需要将整个 XML 文档读入内存来解析单个值 我读过很多文章 坚持认为 SAX 只能用于解析 解码 XML 而不能创建它 这是真的 不 这不是真的 您可以使用类似于以下内容的方式将 XML 编码为
  • 根据另一个列表的顺序对列表进行排序[重复]

    这个问题在这里已经有答案了 我需要对列表进行排序Person对象 List
  • 有没有办法让 SonarQube 只警告不完整的 Switch 语句?

    使用 Java SonarQube 抱怨枚举值上的 switch 语句没有default case 给出的推理是 最终默认条款的要求是防御性编程 该条款应采取适当的行动 或包含 关于为什么不采取行动的适当评论 当开关盖上时 枚举的所有当前值
  • 运行外部进程的非阻塞线程

    我创建了一个 Java GUI 应用程序 它充当许多低级外部进程的包装器 该实用程序按原样运行 但迫切需要一项重大改进 我希望我的外部进程以非阻塞方式运行 这将允许我并行服务其他请求 简而言之 我希望能够在生成数据时处理来自外部进程的数据
  • 返回在 REST 控制器中包装 S3Object.getObjectContent() 的 ResponseEntity 是否安全?

    我正在开发一个 Spring Boot 应用程序 它应该允许用户通过指定的应用程序 REST 接口间接从 Amazon S3 下载文件 为此 我有一个 REST Controller 它向用户返回一个 InputStreamResource
  • Spring验证非空元素的字符串列表

    我有一个模型类 其中包含字符串列表 该列表可以为空 也可以包含元素 如果它有元素 这些元素不能为空 举个例子 假设我有一个名为 QuestionPaper 的类 它有一个 QuestionId 列表 其中每个都是一个字符串 class Qu

随机推荐