LeetCode题目44:通配符匹配【python】

本文介绍了如何使用动态规划、双指针以及贪心算法来解决字符串匹配问题,特别是处理通配符‘?’和‘*’的规则,展示了Python实现,并分析了不同方法的时间和空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
LeetCode解锁1000题: 打怪升级之旅
python数据分析可视化:企业实战案例
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

问题描述

给你一个输入字符串 (s) 和一个字符模式 § ,请你实现一个支持 ‘?’ 和 ‘’ 匹配规则的通配符匹配:
‘?’ 可以匹配任何单个字符。
'
’ 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

示例 1:

输入:s = “aa”, p = “a”
输出:false
解释:“a” 无法匹配 “aa” 整个字符串。
示例 2:

输入:s = “aa”, p = ""
输出:true
解释:'
’ 可以匹配任意字符串。
示例 3:

输入:s = “cb”, p = “?a”
输出:false
解释:‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。

方法一:动态规划

解题步骤
  1. 初始化状态数组:创建一个二维布尔数组 ( dp ) ,其中 ( dp[i][j] ) 表示字符串 ( s ) 的前 ( i ) 个字符是否与模式 ( p ) 的前 ( j ) 个字符匹配。
  2. 处理边界条件
    • ( dp[0][0] = \text{True} ),两个空字符串是可以匹配的。
    • ( dp[i][0] = \text{False} ),空模式不能匹配非空字符串。
    • ( dp[0][j] ),只有当 ( p ) 的前 ( j ) 个字符全部为 ‘*’ 时才为真。
  3. 填充状态数组
    • 如果 ( p[j-1] == ‘?’ ) 或 ( s[i-1] == p[j-1] ),则 ( dp[i][j] = dp[i-1][j-1] )。
    • 如果 ( p[j-1] == ‘’ ),则 ( dp[i][j] = dp[i][j-1] )(不使用 '’) 或 ( dp[i-1][j] )(使用 ‘*’)。
  4. 返回结果:( dp[len(s)][len§] )。
python示例
def isMatch(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 j in range(1, n + 1):
        if p[j - 1] == '*':
            dp[0][j] = dp[0][j - 1]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if p[j - 1] == '?' or s[i - 1] == p[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]
            elif p[j - 1] == '*':
                dp[i][j] = dp[i][j - 1] or dp[i - 1][j]

    return dp[m][n]

# 示例调用
print(isMatch("aa", "a"))  # 输出: False
print(isMatch("aa", "*"))  # 输出: True
算法分析
  • 时间复杂度:(O(M \times N)),其中 (M) 和 (N) 分别是字符串 (s) 和模式 (p) 的长度。
  • 空间复杂度:(O(M \times N)),用于存储状态数组。

方法二:双指针

解题步骤
  1. 初始化指针:分别为字符串 ( s ) 和模式 ( p ) 设置两个指针 ( i ) 和 ( j )。
  2. 跟踪最后一个星号:使用变量记录最后匹配的 ‘*’ 的位置和对应的 ( s ) 的指针位置。
  3. 匹配字符串与模式
    • 如果当前字符匹配或 ( p[j] == ‘?’ ),则 ( i ) 和 ( j ) 都加一。
    • 如果 ( p[j] == ‘*’ ),更新星号匹配的位置,将 ( j ) 增加一,并记录 ( i ) 的位置。
    • 如果不匹配,检查是否存在之前匹配的 ‘',如果有,回溯到最后一个 '’,增加对应的 ( s ) 的指针位置。
  4. 检查剩余模式字符:所有主要字符匹配后,确保模式剩余的都是 ‘*’。
python示例
def isMatch(s: str, p: str) -> bool:
    si, pi = 0, 0
    star_idx = -1
    match = 0
    while si < len(s):
        if pi < len(p) and (s[si] == p[pi] or p[pi] == '?'):
            si += 1
            pi += 1
        elif pi < len(p) and p[pi] == '*':
            star_idx = pi
            match = si
            pi += 1
        elif star_idx != -1:
            pi = star_idx + 1
            match += 1
            si = match
        else:
            return False

    while pi < len(p) and p[pi] == '*':
        pi += 1

    return pi == len(p)

# 示例调用
print(isMatch("aa", "a"))  # 输出: False
print(isMatch("aa", "*"))  # 输出: True
算法分析
  • 时间复杂度:(O(M + N)),其中 (M) 和 (N) 分别是字符串 (s) 和模式 (p) 的长度。在最坏情况下,可能需要回溯,但平均情况下接近线性。
  • 空间复杂度:(O(1)),使用常数空间。

方法三:贪心算法

解题步骤
  1. 初始化指针:与双指针方法类似,使用贪心策略优化处理 ‘*’ 的回溯过程。
  2. 贪心匹配:使用贪心策略在遇到 ‘*’ 时尽可能地延迟匹配,优先匹配后面的字符。
  3. 最后检查:确保没有多余的非 ‘*’ 字符在模式 ( p ) 中残留。
python示例

代码结构与方法二相似,主要优化在处理 ‘*’ 的策略上,尽量减少回溯。

def isMatch(s: str, p: str) -> bool:
    si, pi = 0, 0
    star_idx = -1
    match = 0
    while si < len(s):
        if pi < len(p) and (s[si] == p[pi] or p[pi] == '?'):
            si += 1
            pi += 1
        elif pi < len(p) and p[pi] == '*':
            star_idx = pi
            match = si
            pi += 1
        elif star_idx != -1:
            pi = star_idx + 1
            match += 1
            si = match
        else:
            return False

    while pi < len(p) and p[pi] == '*':
        pi += 1

    return pi == len(p)

# 示例调用
print(isMatch("aa", "a"))  # 输出: False
print(isMatch("aa", "*"))  # 输出: True
算法分析
  • 时间复杂度:(O(M + N)),其中 (M) 和 (N) 分别是字符串 (s) 和模式 (p) 的长度。尽管存在回溯机制,但贪心策略尽可能减少不必要的回溯。
  • 空间复杂度:(O(1)),只使用了几个辅助变量,不依赖于输入大小。

方法三:贪心算法 (续)

这种方法通过贪心算法优化了处理 ‘’ 的回溯过程,通过在每次遇到 '’ 时,记录当前位置并尝试匹配后续字符,如果后续匹配失败,再回到 ‘*’ 的位置尝试更少的字符匹配,直至找到合适的匹配或确定无法匹配。

python示例
def isMatch(s: str, p: str) -> bool:
    s_idx, p_idx = 0, 0  # Pointers for string and pattern
    last_star = -1       # Last position of '*' in p
    match_idx = 0        # Last position matched in s after a '*'

    while s_idx < len(s):
        if p_idx < len(p) and (p[p_idx] == '?' or p[p_idx] == s[s_idx]):
            s_idx += 1
            p_idx += 1
        elif p_idx < len(p) and p[p_idx] == '*':
            last_star = p_idx
            match_idx = s_idx
            p_idx += 1
        elif last_star != -1:
            p_idx = last_star + 1
            match_idx += 1
            s_idx = match_idx
        else:
            return False
    
    # Check for remaining characters in p
    while p_idx < len(p) and p[p_idx] == '*':
        p_idx += 1
    
    return p_idx == len(p)

# 示例调用
print(isMatch("adceb", "*a*b"))  # 输出: True
print(isMatch("acdcb", "a*c?b")) # 输出: False
算法分析
  • 时间复杂度:(O(M + N)),主要时间消耗在初次遍历和处理 ‘*’ 后的回溯上。
  • 空间复杂度:(O(1)),不需要额外的存储空间,所有操作均在原字符串上进行。

这三种方法各有优势和适用场景。动态规划提供了一种稳定的解决方案,适合大多数情况;双指针方法在空间上更优,适用于内存敏感的环境;而贪心算法尝试在运行时优化性能,特别适合需要频繁处理复杂模式的应用。选择哪种方法取决于具体的应用需求、性能考量和开发资源。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据分析螺丝钉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值