学习笔记来自公众号labuladong
①备忘录:
“明确了问题,其实就已经把问题解决了一半。即然耗时的原因是重复计算,那么我们可以造一个「备忘录」”
每次算出某个子问题答案后别急着返回,先记到备忘录中再返回;每次遇到一个子问题先去备忘录查一查,如果之前已经解决过了就直接拿来用,不要重复去计算。
一般使用数组或者哈希表充当备忘录
但带备忘录的递归解法叫做自顶向上,即从上向下延伸,一个规模较大的问题向下逐渐分解规模,直到触底,再逐层返回答案呢
而自底向上,是直接从最底下,最简单,问题规模最小开始往上推,直到推到想要的答案,一般动态规划都是自底向上,“这也是为什么动态规划一般都脱离了递归,而是由循环迭代完成计算”
② dp数组的迭代解法:在这张表上完成自底向上的推算
6/100正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
根据通配符进行大致分类
if(s[i] == p[j] || p[j] == '.')
{
if(j<p.size()-1 && p[j+1] == '*')
{
//有* 可匹配0次或者多次
}else{
//无* 都+1再进行比较
i++; j++;
}
}else
{
//如果有*
if(j<p.size()-1 && p[j+1] == '*')
{
//匹配0次
}else{
//无* 肯定不行了
return false;
}
}
接下来就是在有*的情况下,把所有的可能选择都穷举一遍得到结果
即状态和选择,状态就是i和j的位置,选择就是p[j]选择匹配几个字符
dp(s, i, p, j)=true,表示s[i…]可以匹配p[j…], 根据这个定义我们要的答案就是i= 0, j = 0时的答案,
if(s[i] == p[j] || p[j] == '.')
{
if(j<p.size()-1 && p[j+1] == '*')
{
//有* 可匹配0次或者多次
return dp(s, i ,p, j+2) || dp(s, i+1, j, p);
}else{
//无* 都+1再进行比较
i++; j++;
return dp(s, i+1, p, j+1);
}
}else
{
//如果有*
if(j<p.size()-1 && p[j+1] == '*')
{
//匹配0次
return dp(s, i, p, j+2);
}else{
//无* 肯定不行了
return false;
}
}
再考虑dp的base case 当j走到最后时,i有没有到最后; 到i走到最后时,p剩下的能不能匹配空字符串
注意当能匹配空字符串时,剩下的字符串一定会是偶数个? 而且一定是xyz*这样的形势,即第二个得是*
得到第一题动态规划的题解:
class Solution {
public:
bool dp(string s,int i, string p, int j) {
map<string, bool> memo;
//1先写base case
int m = s.size(), n = p.size();
if(j == n)
{
return i == m;
}
if(i == m)
{
//是否能匹配空字符串
//一定得是偶数个 且是以x*y*z*的形势
if((n-j)%2 == 1)
{
return false;
}
for(; j+1<n; j+=2)
{
if(p[j+1] != '*')
{
return false;
}
}
return true;
}
//2 写状态转移方程
//用备忘录
string key = to_string(i)+"_"+to_string(j);
if(memo.count(key)) return memo[key];
bool res = false;
if(s[i] == p[j] || p[j] == '.')
{
if(j<n-1 && p[j+1] == '*')
{
res = dp(s, i, p, j+2)|| dp(s, i+1, p,j);
}else{
res = dp(s, i+1, p, j+1);
}
}else{
if(j<n-1 && p[j+1] == '*')
{
res = dp(s, i, p, j+2);
}else{
res = false;
}
}
memo[key] = res;
return res;
}
bool isMatch(string s, string p) {
int i = 0, j=0;
return dp(s, i, p, j);
}
};
不过按此方法超出内存限制,打算不再耽搁,往下继续
但凡用到递归的问题,最好都画出递归树,这对分析算法的复杂度,需按照算法是否低效以及低效的原因都有帮助
递归的算法时间复杂度计算:子问题个数乘以解决一个子问题需要的时间