题目描述
回溯
老样子,写不出来动态规划先写暴力递归:
如果没有星号(正则表达式中的 * ),问题会很简单——我们只需要从左到右检查匹配串 s 是否能匹配模式串 p 的每一个字符。
当模式串中有星号时,我们需要检查匹配串 s 中的不同后缀,以判断它们是否能匹配模式串剩余的部分。一个直观的解法就是用回溯的方法来体现这种关系
如果模式串中有星号,它会出现在第二个位置,即 pattern[1]
。这种情况下,我们可以直接忽略模式串中这一部分,或者删除匹配串的第一个字符,前提是它能够匹配模式串当前位置字符,即pattern[0]
。如果两种操作中有任何一种使得剩下的字符串能匹配,那么初始时,匹配串和模式串就可以被匹配。
bool isMatch(string s, string p) {
if(p.size() == 0) return !s.size();
bool rs = (!s.empty() && (s[0] == p[0] || p[0] == '.'));
if(p.size() >= 2 && p[1] == '*')
return (rs && isMatch(s.substr(1,s.size()-1), p)) || isMatch(s, p.substr(2,p.size()-2));
else
return rs && isMatch(s.substr(1,s.size()-1), p.substr(1,p.size()-1));
}
记忆化
直接在回溯的基础上增加记忆化,记录下已经算出结果的状态
map<string,bool> mp;
bool isMatch(string s, string p) {
if(p.size() == 0) return !s.size();
return dfs(s,p,0,0);
}
bool dfs(const string& s,const string& p, int i, int j)
{
string key = to_string(i) + "_" + to_string(j);
if(mp.find(key) != mp.end()) return mp[key];
bool ans;
if( j == p.size())
ans = i == s.size();
else
{
bool rs = (i < s.size() && (s[i] == p[j] || p[j] == '.'));
if(j + 1 < p.size() && p[j+1] == '*')
ans = (rs && dfs(s, p,i+1,j)) || dfs(s, p,i,j+2);
else
ans = rs && dfs(s, p,i+1,j+1);
}
mp.insert({key,ans});
return ans;
}
动态规划
根据记忆化改迭代(记忆化为自顶向下方法,迭代为自底向上方法)
bool isMatch(string s, string p) {
vector<vector<bool>> dp(s.size()+1, vector<bool>(p.size() + 1, false));
dp[s.size()][p.size()] = true;
for (int i = s.size(); i >= 0; i--) {
for (int j = p.size() - 1; j >= 0; j--) {
bool first_match = (i < s.size() && (p.at(j) == s.at(i) || p.at(j) == '.'));
if (j + 1 < p.size() && p.at(j + 1) == '*') {
dp[i][j] = dp[i][j + 2] || first_match && dp[i + 1][j];
}
else {
dp[i][j] = first_match && dp[i + 1][j + 1];
}
}
}
return dp[0][0];
}
时间复杂度:用 TT 和 PP 分别表示匹配串和模式串的长度。对于i=0, … , Ti=0,…,T 和 j=0, … , Pj=0,…,P 每一个 dp(i, j)只会被计算一次,所以后面每次调用都是 O(1)O(1) 的时间。因此,总时间复杂度为 O(TP)O(TP) 。
空间复杂度:我们用到的空间仅有 O(TP)O(TP) 个 boolean 类型的缓存变量。所以,空间复杂度为 O(TP)O(TP) 。