[leetcode] 44. Wildcard Matching

本文探讨了带通配符的模式匹配问题,包括使用动态规划和优化后的回溯法两种解决方案。介绍了这两种方法的基本思路及其实现过程,并对比了它们的时间效率。

Implement wildcard pattern matching with support for '?' and '*'.

'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false

这道题是带通配符的模式匹配,题目难度为Hard。

直观的想法是采用回溯法或动态规划来解决题目,试了下回溯法超时了,这里就不列出代码了。用match[i][j]表示s[0~i-1]能否和p[0~j-1]匹配,如果p[j-1]为'*',由于'*'可以匹配任意长度(包含0),若匹配长度为0,则match[i][j] = match[i][j-1],若匹配长度不为0,则match[i][j] = match[i-1][j],这里用匹配长度1表示是因为可以逐个递推下去;如果p[j-1]为'?'或s[i-1]==p[j-1],则match[i][j] = match[i-1][j-1]。这样通过动态规划的方法即可得出最终能否匹配,具体代码:

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> match(m+1, vector<bool>(n+1, false));
        
        match[0][0] = true;
        for(int i=1; i<=m; ++i) match[i][0] = false;
        for(int i=1; i<=n; ++i) match[0][i] = match[0][i-1] && (p[i-1]=='*');
        for(int i=1; i<=m; ++i) {
            for(int j=1; j<=n; ++j) {
                if(p[j-1] == '*')
                    match[i][j] = match[i-1][j] || match[i][j-1];
                else if(p[j-1] == '?' || s[i-1] == p[j-1])
                    match[i][j] = match[i-1][j-1];
                else 
                    match[i][j] = false;
            }
        }
        
        return match[m][n];
    }
};
虽然通过了测试,不过耗时非常长,没有想到更高效的方法。看了别人的代码,效率高的方法依然采用回溯法来解决题目,只是在回溯过程中进行了优化。匹配过程中记录下'*'出现的位置,在后续不能继续匹配时回溯到此位置让'*'匹配更多的字符来继续后续匹配。具体代码:
class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        int i = 0, j = 0, sPos = -1, pPos = -1;
        while(i < m) {
            if(j < n && (s[i] == p[j] || p[j] == '?')) {
                ++i;
                ++j;
            }
            else if(j < n && p[j] == '*') {
                sPos = i;
                pPos = ++j;
            }
            else if(pPos >= 0) {
                i = ++sPos;
                j = pPos;
            }
            else return false;
        }
        while(j < n && p[j] == '*') ++j;
        return j == n;
    }
};
这里回溯只进行了一层,为何能去掉之前'*'位置的回溯呢?这也成为能否写出以上代码的关键。按照上述方法,假如在模式中匹配到两个'*'后出现了不匹配,第二个'*'之前部分记为p[0~j],0~j之间还有一个'*',p[0~j]匹配的字符串记为s[0~i],此时s[0~i]是p[0~j]能够匹配的最短字符串,回溯到第二个'*'位置时让'*'匹配的字符加1,即从s[i+1]继续匹配。如果回溯到第一个'*'位置,再次匹配到p[0~j]时字符串为s[0~k],则k>i,因为回溯时至少让第一个'*'匹配的字符加1。然后从s[k+1]继续匹配,而k>i,所以所有这些情况在回溯到第二个'*'时都覆盖到了,因此在回溯时只需要回溯到最近的'*'位置即可,通过这种策略极大地减少了回溯分支,效率得到了很大提升。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值