通配符匹配

一、前言

问题来源LeetCode 44,难度:困难

问题链接:https://leetcode-cn.com/problems/wildcard-matching

 

二、题目

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。

'?' 可以匹配任何单个字符。 
'*' 可以匹配任意字符串(包括空字符串)。

两个字符串完全匹配才算匹配成功。

说明:

s 可能为空,且只包含从 a-z 的小写字母。

p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

示例 3:

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

示例 4:

输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

示例 5:

输入:
s = "acdcb"
p = "a*c?b"
输出: false

 

三、分析

3.1 推断

设:字符串s长度为sl,字符串p长度为pl

设:DP[i] = DS[j] 表示 p[0 - i] 和 s[o - j] 是匹配的,其中 0 <= i < pl, 0 <= j < sl

情况 1)如果 DP[i] = DS[j] && p[i+1] = '*', 可以推断出 DP[i] = DS[j+k],其中 j <= j+k < sl

情况 2)如果 DP[i] = DS[j] && p[i+1] = '?', 可以推断出 DP[i+1] = DS[j+1]

情况 3)如果 DP[i] = DS[j] && p[i+1] = 一个字符, 可以推断出 当 p[i+1] = s[j+1] (s[j+1]如果是字符需要和p[i+1] ,如果是*或?也满足相等)那么 DP[i+1] = DS[j+1]

后面的结果取决于前面的结果和当前节点值,我们需要记录已经遍历结果。这是自底向上确定结果。用图形表示记录的结果,红色是已经匹配的。

情况一可以用下图表示:

情况二可以用下图表示:

情况三可以用下图表示,需要注意情况三和情况二很像,不同的是情况三需要他们当前点相等

3.2 举例演示:

输入:
s = "abcebdk"
p = "*a*b?k"

初始图形其中#表示空:

第一次搜索

第二次搜索

第三次搜索

第四次搜索

第五次搜索

第六次搜索

第七次搜索

字符串 s和p匹配

 

四、解决方案

有两种解决方法,暴力解决就不考虑了。

方法一:如上面分析,可以用动态规划解决

方法二:观察《3.1 推断》可以看出,DP[i] = DS[j] ,我们只需要搜索 (i,lp)和(j,ls)即可,并不需要每一行都搜索。如下图,如果DP[2] = DS[1],那么我们只需要在,横坐标:[2,8],纵坐标:[3,7]之间搜索即可。如果不成立,回溯到下一个DP[i2] = DS[j2] 开始搜索。

 

五、编码实现

//==========================================================================
/**
* @file : 44_IsMatch.h
* @title: 通配符匹配
* @purpose : 给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。
*			 '?' 可以匹配任何单个字符。
*			 '*' 可以匹配任意字符串(包括空字符串)。
*			 两个字符串完全匹配才算匹配成功。
*
* 说明:
*
* s 可能为空,且只包含从 a-z 的小写字母。
* p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
* 示例 1:
* 输入:
* s = "aa"
* p = "a"
* 输出: false
* 解释: "a" 无法匹配 "aa" 整个字符串。
*
* 示例 2:
* 输入:
* s = "aa"
* p = "*"
* 输出: true
* 解释: '*' 可以匹配任意字符串。
*
* 示例 3:
* 输入:
* s = "cb"
* p = "?a"
* 输出: false
* 解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
* 
* 示例 4:
* 输入:
* s = "adceb"
* p = "*a*b"
* 输出: true
* 解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".
*
* 示例 5:
* 输入:
* s = "acdcb"
* p = "a*c?b"
* 输出: false
*
* 来源:力扣(LeetCode)难道:困难
* 链接:https://leetcode-cn.com/problems/wildcard-matching
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
//==========================================================================

#include <vector>
#include <iostream>
using namespace std;

#define NAMESPACE_ISMATCH namespace NAME_ISMATCH {
#define NAMESPACE_ISMATCHEND }
NAMESPACE_ISMATCH

// 方法一:动态规划
// 时间复杂度:O(SP),空间复杂度:O(SP)。其中S和P指的是字符模式和输入字符串的长度。
class Solution_1
{
public:
    bool isMatch(string s, string p)
    {
        int sLen = (int)s.length(), pLen = (int)p.length();

        if ((p == s) || p == "*") return true;
        if (p.empty() || s.empty()) return false;

        vector<vector<bool>> d(pLen + 1, vector<bool>(sLen + 1, false));
        d[0][0] = true;

        for (int pIdx = 1; pIdx < pLen + 1; pIdx++)
        {
            if (p[pIdx - 1] == '*')
            {
                int sIdx = 0;
                while ((sIdx <= sLen) && (!d[pIdx - 1][sIdx])) sIdx++;
                while (sIdx <= sLen) d[pIdx][sIdx++] = true;
            }
            else if (p[pIdx - 1] == '?')
            {
                for (int sIdx = 1; sIdx < sLen + 1; sIdx++)
                    d[pIdx][sIdx] = d[pIdx - 1][sIdx - 1];
            }
            else
            {
                for (int sIdx = 1; sIdx < sLen + 1; sIdx++)
                {
                    d[pIdx][sIdx] = d[pIdx - 1][sIdx - 1] &&
                        (p[pIdx - 1] == s[sIdx - 1]);
                }
            }
        }
        return d[pLen][sLen];
    }
};

// 方法二:回溯
// 时间复杂度:O(S logP),空间复杂度:O(1)。其中S和P指的是字符模式和输入字符串的长度。
class Solution_2 
{
public:
    bool isMatch(string s, string p)
    {
        int sLen = (int)s.length(), pLen = (int)p.length();
        int sIdx = 0, pIdx = 0;
        int starIdx = -1, sTmpIdx = -1;

        while (sIdx < sLen) 
        {
            if (pIdx < pLen && (p.at(pIdx) == '?' || p.at(pIdx) == s.at(sIdx))) 
            {
                ++sIdx;
                ++pIdx;
            }
            else if (pIdx < pLen && p.at(pIdx) == '*') 
            {
                starIdx = pIdx;
                sTmpIdx = sIdx;
                ++pIdx;
            }
            else if (starIdx == -1) 
            {
                return false;
            }
            else 
            {
                pIdx = starIdx + 1;
                sIdx = sTmpIdx + 1;
                sTmpIdx = sIdx;
            }
        }

        for (int i = pIdx; i < pLen; i++)
            if (p.at(i) != '*') return false;
            
        return true;
    }
};

//
// 测试 用例 START
void test(const char* testName, string s, string p, int expect)
{
    Solution_1 S1;
    Solution_2 S2;

    int result1 = S1.isMatch(s, p);
    int result2 = S2.isMatch(s, p);

    if (result1 == expect && result2 == expect)
    {
        cout << testName << ", solution12 passed." << endl;
    }
    else
    {
        cout << testName << ", solution failed. result1:" << result1 << ",result2:" << result2 << endl;
    }
}

// 测试用例
void Test1()
{
    string s = "aa";
    string p = "a";
    bool expect = false;

	test("Test1()", s, p, expect);
}

// 测试用例
void Test2()
{
    string s = "aa";
    string p = "*";
    bool expect = true;

    test("Test2()", s, p, expect);
}

// 测试用例
void Test3()
{
    string s = "cb";
    string p = "?a";
    bool expect = false;

    test("Test3()", s, p, expect);
}

// 测试用例
void Test4()
{
    string s = "adceb";
    string p = "*a*b";
    bool expect = true;

    test("Test4()", s, p, expect);
}

// 测试用例
void Test5()
{
    string s = "acdcb";
    string p = "a*c?b";
    bool expect = false;

    test("Test5()", s, p, expect);
}

// 测试用例
void Test6()
{
    string s = "abcebdk";
    string p = "*a*b?k";
    bool expect = true;

    test("Test6()", s, p, expect);
}

NAMESPACE_ISMATCHEND
// 测试 用例 END
//

void IsMatch_Test()
{
    NAME_ISMATCH::Test1();
    NAME_ISMATCH::Test2();
    NAME_ISMATCH::Test3();
    NAME_ISMATCH::Test4();
    NAME_ISMATCH::Test5();
    NAME_ISMATCH::Test6();
}

执行结果:

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值