题目
给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ? 和 * 的通配符匹配
匹配规则:
相同字符可以匹配
‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
解题思路
动态规划
递推函数
定义dp[i][j]表示字符串s的前i个字符和匹配字符串p的前j个字符的匹配情况,
初始情况dp[i][0]全为false,dp[0][j]由p[j]得出,如果p的前j个字符都为’*'则dp[0][j]为true。
状态转移方程
当p[j]为字母,则判断p[j]和s[i]是否相等,相等则dp[i][j]=dp[i-1][j-1],不相等则dp[i][j]为false
当p[j]为’?’,则dp[i][j]=dp[i-1][j-1]
当p[j]为’’,则p[i][j]=p[i-1][j] || p[i][j-1],即p[i-1][j]和p[i][j-1]两种情况有一种情况为真都可以推导出p[i][j]为真,p[i-1][j]代表’‘匹配了s[i]这个字母的情况,p[i][j-1]则代表’*'没有匹配s[i]这个字母的情况。
代码:
bool isMatch(string s, string p) {
int l1=s.length();
int l2=p.length();
bool dp[1000][1000];
for(int i=0;i<=l1;i++){
dp[i][0]=false;//当p长度为空的时候,空模式不能匹配任何字符串
}
for(int i=1;i<=l2;i++){
if(p[i-1]=='*'){
dp[0][i]=true;
}else{
for(;i<=l2;i++){
dp[0][i]=false;
}
break;
}
}
dp[0][0]=true;
for(int i=1;i<=l1;i++){
for(int j=1;j<=l2;j++){
if(p[j-1]=='*'){
dp[i][j]=dp[i-1][j]||dp[i][j-1];
}else if(p[j-1]=='?'){
dp[i][j]=dp[i-1][j-1];
}else{
if(s[i-1]==p[j-1]){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=false;//提交代码没加这句话导致错误
}
}
}
}
return dp[l1][l2];
}
遇到问题:
1) 在提交时后显示超时,去掉代码中的cout后解决
2) 自己测试输出正确,提交后输出却错误,经测试发现新建dp的时候,测试环境好像默认dp为1了,所以提交代码在当p[j]为字母且p[j]不等于s[i]的情况没有做任何处理的话,会导致出错。
贪心算法
自己的做法
稍微看了一下题解就开始自己写了,思路大致为因为匹配字符串p中可以根据*分成若干个子字符串,对于 * a * b *这种情况,我们只要遍历s,依次找到匹配这些字符串,如果都找到了则表示匹配成功,因为 * 可以匹配任何字符串,但是对于a * b * 、 * a * b和a * b这种情况,就要单独测试了,下面是我自己写的代码(写的很烂、勿喷)
bool sub_match(string s,string p,int l){
for(int i=0;i<l;i++){
if(p[i]=='?'){
continue;
}else if(s[i]!=p[i]){
cout<<s[i]<<' '<<p[i]<<' '<<(p[i]=='?')<<endl;
return false;
}
}
return true;
}
bool isMatch(string s, string p){
string orip = p;
int l1=s.length();
int l2=p.length();
if(l1==0&&l2==0)return true;
if(l2==0)return false;//空模式不能匹配任何字符串
for(int i=0;i<l2;i++){
if(p[i]=='*'){
p[i]=' ';
}
}
stringstream ss(p);
string p_list[5000];
int index=0;
while(ss>>p_list[index]){
index++;
}
//处理特殊情况
if(index==0){//表示p为全*
return true;
}
if(orip[0]!='*'){//p开头不是*则需要匹配第一个子字符串是否匹配s对应的开头子字符串
if(!sub_match(s.substr(0,p_list[0].length()),p_list[0],p_list[0].length())){
return false;
}
}
int index2=0,lastindex=0;
for(int i=0;i<l1;i++){
if(p_list[index2][0]==s[i]||p_list[index2][0]=='?'){
int tl=p_list[index2].length();
string ts;
if(i+tl-1<=s.length()){
ts=s.substr(i,tl);
}else{
return false;
}
if(tl!=ts.length())return false;
if(sub_match(ts,p_list[index2],tl)){
index2++;
i=i+tl-1;
lastindex=i;//记录最后一次s匹配子字符串的结尾位置
}
}
}
if(index2==index&&lastindex==(l1-1)||index2==index&&orip[l2-1]=='*'){//所有子字符串都匹配成功,同时s末尾没有元素未匹配或者p末尾为*
return true;
}else if(index2==index&&(orip[0]=='*'||index2!=1)){//所有字符串都匹配成功,但是s末尾有字符未匹配同时p末尾不是*,则需要判断最后一个子字符串是否能匹配s的末尾对应子字符串
int tl = p_list[index2-1].length();
if(sub_match(s.substr(l1-tl,tl),p_list[index2-1],tl)){
return true;
}
}
return false;
}
遇到的问题
主要就是不是* a * b *这种情况的p的处理,以及p的子字符串中含有?的处理,对应leetcode测试环境上的各种样例。
官方做法
官方做法的思路是类似的,不过代码更精巧
详细看https://leetcode-cn.com/problems/wildcard-matching/solution/tong-pei-fu-pi-pei-by-leetcode-solution/
这里贴上官方代码:
bool isMatch(string s, string p) {
auto allStars = [](const string& str, int left, int right) {
for (int i = left; i < right; ++i) {
if (str[i] != '*') {
return false;
}
}
return true;
};
auto charMatch = [](char u, char v) {
return u == v || v == '?';
};
while (s.size() && p.size() && p.back() != '*') {//s不为空,p不为空且p的末尾不为*,这里是用来判断p末尾没有*的情况,如果p末尾没有*,则先把末尾子字符串匹配
if (charMatch(s.back(), p.back())) {//如果s和p的尾部字符匹配,直到p的尾部字符为*
s.pop_back();
p.pop_back();
}
else {
return false;
}
}
if (p.empty()) {//如果p为空,则只有s为空时才为真
return s.empty();
}
int sIndex = 0, pIndex = 0;
int sRecord = -1, pRecord = -1;
while (sIndex < s.size() && pIndex < p.size()) {//然后就当做首尾都为*的情况操作了
if (p[pIndex] == '*') {
++pIndex;
sRecord = sIndex;
pRecord = pIndex;
}
else if (charMatch(s[sIndex], p[pIndex])) {
++sIndex;
++pIndex;
}
else if (sRecord != -1 && sRecord + 1 < s.size()) {//这里的sRecord! = -1就是用来判断p开头没有*的情况,p开头没有*号的话
++sRecord;
sIndex = sRecord;
pIndex = pRecord;
}
else {
return false;
}
}
return allStars(p, pIndex, p.size());
}