当前位置: 首页 > news >正文

LeetCode算法日记 - Day 104: 通配符匹配

目录

1. 通配符匹配

1.1 题目解析

1.2 解法

1.3 代码实现


1. 通配符匹配

https://leetcode.cn/problems/wildcard-matching/description/

给你一个输入字符串 (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 仅由小写英文字母、'?' 或 '*' 组成

1.1 题目解析

题目本质
通配符匹配判定问题——在有 ?、* 通配符的前提下,判断模式串 p 是否能完整匹配文本串 s。

常规解法
直观想法:从左到右递归匹配

  • 普通字符:必须完全相同

  • ?:可以匹配当前任意一个字符

  • *:可以匹配 0 个、1 个、多个字符,需要枚举匹配长度
    即写一个递归函数 dfs(i, j) 表示 s[i..] 与 p[j..] 是否能匹配

class Solution {public boolean isMatch(String s, String p) {int n = s.length(), m = p.length();return dfs(s, p, 0, 0);}private boolean dfs(String s, String p, int i, int j) {int n = s.length(), m = p.length();// 模式串用完:只有 s 也用完才算匹配if (j == m) return i == n;char pc = p.charAt(j);if (pc == '*') {// * 匹配空串:j 前进一位// * 匹配一个字符:i 前进一位,j 原地if (dfs(s, p, i, j + 1)) return true;return i < n && dfs(s, p, i + 1, j);} else {// 需要 s 还有字符,并且当前能匹配if (i < n && (pc == '?' || pc == s.charAt(i))) {return dfs(s, p, i + 1, j + 1);} else {return false;}}}
}

问题分析
递归 + 暴力枚举 * 匹配的长度,会产生大量重复子问题:

  • 每遇到一个 *,分支就会倍增(0 个字符、1 个字符、2 个字符……)

  • 最坏情况下时间复杂度接近指数级 O(2^(n+m))
    对于 n, m ≤ 2000 的数据规模,很容易超时。

思路转折
要想高效 → 必须避免重复计算 → 引入状态表示(记忆化 / DP)。注意到状态只由“当前匹配到 s 的第几位、p 的第几位”决定,可以定义:

  • 状态 (i, j):s[0..i-1] 和 p[0..j-1] 是否匹配

  • 将所可能的 (i, j) 放入一张 dp 表中,用迭代的方式自底向上填表
    → 将指数时间降为多项式时间 O(n * m)。

1.2 解法

算法思想

dp[i][j] 表示:s 的前 i 个字符(s[0..i-1])
是否可以被 p 的前 j 个字符(p[0..j-1])完全匹配。

步骤拆解

i)创建 DP 表:boolean[][] dp = new boolean[n + 1][m + 1];

ii)初始化边界:

  • dp[0][0] = true
    空串与空模式匹配。

  • 第一行 dp[0][j](空串 vs 模式前缀):
    只有当前缀全是 * 时,才可能匹配空串:

iii)动态规划填表

iv)返回结果:返回 dp[n][m],表示整个 s 和整个 p 是否能完全匹配。

易错点

  • dp[0][j] 的含义:是“模式前缀能否匹配空串”,只有“前缀全为 *”时才为 true,其它必须是 false。

  • 初始化第一行时,if 后面一定要写 else,不要像之前漏写导致每次循环都把 dp[0][j] 重置为 false。

  • 不要照搬“不同的子序列”那题,把 dp[i][0] 初始化为 1 或 true,本题里 dp[i][0](i>0)含义是“非空串 vs 空模式”,应该全是 false。

  • * 的转移一定要包含两个方向:

    • dp[i][j-1] → * 匹配空串

    • dp[i-1][j] → * 吃掉一个字符并继续匹配

1.3 代码实现

class Solution {public boolean isMatch(String _s, String _p) {int n = _s.length();int m = _p.length();char[] s = _s.toCharArray();char[] p = _p.toCharArray();// 1) 创建 DP 表:dp[i][j] 表示 s[0..i-1] 是否能被 p[0..j-1] 匹配boolean[][] dp = new boolean[n + 1][m + 1];// 2) 初始化:空串 vs 空模式dp[0][0] = true;// 2.1 初始化第一行:空串 vs 模式前缀for (int j = 1; j <= m; j++) {if (p[j - 1] == '*') {// 只有前缀全为 * 时才能匹配空串dp[0][j] = dp[0][j - 1];} else {dp[0][j] = false;}}// 3) 动态规划填表for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (p[j - 1] == s[i - 1] || p[j - 1] == '?') {// 普通字符或 ? :匹配当前这个字符,取决于前一位是否匹配dp[i][j] = dp[i - 1][j - 1];} else if (p[j - 1] == '*') {// * 匹配空串:dp[i][j - 1]// * 匹配一个或多个字符:dp[i - 1][j]dp[i][j] = dp[i][j - 1] || dp[i - 1][j];}}}// 4) 返回最终结果return dp[n][m];}
}

复杂度分析

  • 时间复杂度:O(n × m),需要填满一个 (n+1) × (m+1) 的 DP 表

  • 空间复杂度:O(n × m),DP 表的存储空间(可以进一步优化为一维,但对理解不重要)

http://www.dtcms.com/a/613682.html

相关文章:

  • RDMA内存保护概念---MR,PD
  • 11月13号作业
  • 怎样建立网站目录结构炒股网站开发
  • 【STM32MP157 异核通信框架学习篇】(10)Linux下Remoteproc相关API (上)
  • 东莞企业建站平台中企动力 做网站 怎么样
  • 虚拟机的未来:从云计算到量子模拟
  • 前端响应式设计资源,框架+模板
  • 品牌网站建设服务网络品牌塑造
  • C语言编译系统 | 高效编译与优化技术分析
  • L2层差错控制与HARQ协议介绍
  • 4. Qt深入 线程和QObject
  • 印尼游戏出海合规指南:法律框架、税务政策与运营挑战
  • 【Java Web学习 | 第11篇】JavaScript(5)BOM
  • 打造您专属的高效DNS解析器:RethinkDNS
  • 网上书店网站建设方案策划如何建设好一个网站
  • Spring Framework 中文官方文档
  • 深度剖析 C++ vector的底层实现
  • USDe:站在稳定币、永续化与资产代币化三大趋势交汇点的新型美元
  • SpringBoot 2.x 升级到 3.x 时 Swagger 迁移完整指南
  • 网站首页浮动窗口代码忘记了wordpress登录密码忘记
  • springMVC(3)学习
  • 负载均衡API测试
  • 门户类网站费用淘宝网站边上的导航栏怎么做
  • oralce创建种子表,使用存储过程生成最大值sql,考虑并发,不考虑并发的脚本,plsql调试存储过程,java调用存储过程示例代码
  • 计算机网络技术三级知识点
  • 好用心 做网站送女友wordpress英文主题出现汉字
  • 建筑网站夜里几点维护个人网站名字大全
  • 18.HTTP协议(二)
  • 【科技补全76】新概念英语点读工具NCE-Flow、在线文件管理器copyparty 部署指北
  • 添加某些应用程序使其能够用win+r启动