一、前言
问题来源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();
}
执行结果: