暂时pass的题目的学习笔记(按类型分类 ):动态规划、递归

动态规划类

学习笔记来自公众号labuladong

  • 动态规划的一般形式就是求最值——其核心问题是穷举
  • 但动态规划的穷举有些特别,因为这类问题存在重叠子问题 如果暴力穷举的话效率会极其低下,所以需要**「备忘录」或者「DP table」**来优化穷举过程,避免不必要的计算
  • 动态规划问题一定具备最优子结构,才能通过子问题的最值得到原问题的最值,要符合“最优子结构”,子问题间必须互相独立。
  • 只有正确列出状态转移方程才能正确地穷举
  • 明确「状态」 -> 定义 dp 数组/函数的含义 -> 明确「选择」-> 明确 base case
  1. 重叠子问题可以理解为重复计算的部分,解决重叠子问题可以通过备忘录和dptable:

①备忘录:
“明确了问题,其实就已经把问题解决了一半。即然耗时的原因是重复计算,那么我们可以造一个「备忘录」”
每次算出某个子问题答案后别急着返回,先记到备忘录中再返回;每次遇到一个子问题先去备忘录查一查,如果之前已经解决过了就直接拿来用,不要重复去计算。
一般使用数组或者哈希表充当备忘录
但带备忘录的递归解法叫做自顶向上,即从上向下延伸,一个规模较大的问题向下逐渐分解规模,直到触底,再逐层返回答案呢
自底向上,是直接从最底下,最简单,问题规模最小开始往上推,直到推到想要的答案,一般动态规划都是自底向上,“这也是为什么动态规划一般都脱离了递归,而是由循环迭代完成计算”

② dp数组的迭代解法:在这张表上完成自底向上的推算

正则表达式匹配

6/100正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
暂时pass的题目的学习笔记(按类型分类 ):动态规划、递归_第1张图片

根据通配符进行大致分类

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);
        
    }
};

不过按此方法超出内存限制,打算不再耽搁,往下继续

递归

但凡用到递归的问题,最好都画出递归树,这对分析算法的复杂度,需按照算法是否低效以及低效的原因都有帮助
递归的算法时间复杂度计算:子问题个数乘以解决一个子问题需要的时间

你可能感兴趣的:(刷题新航路!,学习,笔记,动态规划)