【leetcode】Dp高频题目整理

185 阅读5分钟

DP

树形

  • 【337】打家劫舍 III:dfs+返回最大、不偷此节点的收益

    • 状态计算:max(root.Val + l2 + r2,l1 + r1), l1 + r1

一维

  • 斐波那契数列:f[i]=f[i-1]+f[i-2]

    • 【70】爬楼梯
  • 卡特兰数:

    •   f(n) =i=1nf(i1)f(ni)=4n2n+1f(n1)f(n) = \sum\limits_{i = 1}^n {f(i - 1)*f(n - i)} = \frac{{4n - 2}}{{n + 1}}f(n - 1)
  • 【91】解码方法:

    • 集合 & 属性:dp[i]表示前i个数字拆分的方案数

    • 状态计算:枚举1~26看后缀是否匹配,如果匹配

      • dp[i]+=dp[i-位数]
  • 【96】不同的二叉搜索树

  • 【139】单词拆分、【offer 46】把数字翻译成字符串

    • 集合 & 属性:m[i]表示前i个字母是不是可以拆分

    • 状态计算:枚举词典,后缀与m[:i]重叠时,

      • m[i] = m[i] || m[i-len(k)]
  • 【198】打家劫舍:dp[i] 偷前i家的最大收益,不能偷连续的两家

    • dp[i] = max(dp[i-1],dp[i-2]+nums[i])
  • 【213】打家劫舍 II:不能同时偷首位,进行nums[0:len(nums)-1]和nums[1:]两次打家劫舍取最大值即可

  • 【300】最长递增子序列:

    • 集合dp[i]:以nums[i]结尾的所有递增序列

    • 属性:所有递增序列的最长的长度

    • 状态计算:枚举0~i-1,如果dp[i]>dp[j]

      • dp[i] = max(dp[i], dp[j]+1)
  • 【152】乘积最大子数组

    • 集合&属性:maxx[i]:以nums[i]结尾的乘积最大值,minn[i]:以nums[i]结尾的乘积最大值
    • 状态计算:maxx[i] = max(nums[i],nums[i]*maxx[i-1],nums[i]*minn[i-1])
  • 【264】丑数 II,【offer 49】丑数

    • 集合&属性:dp[i]为第i个丑数的值
    • 状态计算:dp[i]=min(2*dp[l2],3*dp[l3],5*dp[l5])
  • 【279】完全平方数:

    • 集合&属性:m[j]为j可以拆分成完全平方数的最少数目
    • 状态计算:枚举所有小于等于j的完全平方数:m[j] = min(m[j],m[j-i*i]+1)
  • 【343】整数拆分:

    • 集合&属性:m[i]为i可拆分的最大的乘机
    • 状态计算:从2开始枚举切分的大小,m[i] = max(m[i],max(j*m[i-j],j*(i-j)))

二维

  • 【10】正则表达式匹配 😡

    • 集合f[i][j]:所有s前i个字母与p前j个字母的匹配方案

    • 属性:bool,是否有一个方案匹配

    • 状态计算

      • p[j] != '*':f[i][j] = (p[j]=='.' || s[i-1]==p[j-1]) && f[i-1][j-1]

      • p[j]='':枚举前字母出现0、1、2...次

        •       f[i][j]=f[i][j2]f[i1][j3]&(s[i]=p[j1]p[j1]=.)f[i2][j4]&(s[i]=p[j1]&s[i1]=p[j1]p[j1]=.)f[i1][j]=f[i1][j3]f[i2][j4]&(s[i1]=p[j1]p[j1]=.)f[i][j]=f[i][j2]f[i1][j]&(s[i]=p[j1]p[j1]=.)\begin{array}{l} f[i][j] = f[i][j - 2]||f[i - 1][j - 3]\& (s[i] = p[j - 1]||p[j - 1] = '.')||f[i - 2][j - 4]\& (s[i] = p[j - 1]\& s[i - 1] = p[j - 1]||p[j - 1] = '.') \ldots \\ f[i - 1][j] = f[i - 1][j - 3]||f[i - 2][j - 4]\& (s[i - 1] = p[j - 1]||p[j - 1] = '.') \ldots \\ f[i][j] = f[i][j - 2]||f[i - 1][j]\& (s[i] = p[j - 1]||{\rm{ }}p[j - 1] = '.') \end{array}
    • s = " "+s
      p = " "+p
      f:=make([][]bool,len(s))
      for i:=0;i<len(s);i++{
          f[i] = make([]bool,len(p))
      }
      f[0][0] = true
      for i:=0;i<len(s);i++{
          for j:=1;j<len(p);j++{
              if j+1<len(p) && p[j+1]=='*'{
                  continue
              }
              if p[j]!='*' && i>0 {
                  f[i][j] = f[i-1][j-1] && (s[i]==p[j] || p[j]=='.')
              }else if p[j]=='*'{
                  f[i][j] = f[i][j-2] || i>0 && f[i-1][j] && (s[i]==p[j-1] || p[j-1]=='.')
              }
          }
      }
      
  • 【44】通配符匹配:

    • 集合 & 属性:dp[i][j]为s前i个字母与p前j个字母是否匹配

    • 状态计算:

      • p[j]==s[i] || p[j]=='?':dp[i][j] = dp[i-1][j-1]

      • p[j]=='*'时,dp[i][j]=dp[0][j-1]||dp[1][j-1]||...||dp[i][j-1]

        • dp[i-1][j]=dp[0][j-1]||dp[1][j-1]||...||dp[i-1][j-1]
        • 所以:dp[i][j]=dp[i][j-1]||dp[i-1][j]
  • 【62】不同路径、【63】不同路径 II

    • 集合 & 属性:dp[i][j]为机器人到达ij的路径数
    • 状态计算:dp[i][j] = dp[i-1][j]+dp[i][-1]
  • 【64】最小路径和

    • 集合 & 属性:dp[i][j]到达ij的最短路径
    • 状态计算:dp[i][j] = m[i][j]+min(dp[i-1][j],dp[i][-1])
  • 【72】编辑距离

    • 集合:f[i][j]:word1前i个字母和word2前j个字母所有编辑的情况

    • 属性:int,所有编辑情况中最小的

    • 状态计算:

      • word1[i]=word[j]:f[i][j]=f[i-1][j-1]
      • word1[i]!=word[j]:f[i][j]=1+min(f[i-1][j-1],f[i-1][j],f[i][j-1])
  • 【97】交错字符串:

    • 集合&属性:dp[i][j]为s1前i和s2前j是否可以组成s3,先遍历得到dp[i][0]、dp[0][i]

    • 状态计算:枚举i、j

      • 如果s1[i-1]==s3[i+j-1]:dp[i][j] |= dp[i-1][j]
      • 如果s2[i-1]==s3[i+j-1]:dp[i][j] |= dp[i][j-1]
  • 【115】不同的子序列:

    • 集合&属性:dp[i][j]为s前i个字母与t前j个字母匹配的数目,dp[i][0]=1

    • 状态计算:dp[i][j]+=dp[i-1][j]

      • 如果s[i]==t[j]:dp[i][j]+=dp[i-1][j-1]
  • 【120】三角形最小路径和:triangle[i][j]+=min(triangle[i+1][j],triangle[i+1][j+1])

  • 【221】最大正方形

    • ans[i][j]:ij为右下角的最大正方形的边长
    • ans[i][j] = min(ans[i-1][j],min(ans[i-1][j-1],ans[i][j-1]))+1
  • 【718】最长重复子数组、【1143】最长公共子序列

    • 集合:dp[i][j]:nums1第i个数与nums2第j个数结尾的所有子数组的集合

    • 属性:子数组长度最长的

    • 状态计算:如果nums1[i]==nums2[j]

      • dp[i][j] = max(dp[i-1][j-1]+1,dp[i][j])

背包

01背包

  • 【416】分割等和子集

完全背包

  • 【322】零钱兑换

    • for i := 0; i < len(coins); i++ {
          for j := coins[i]; j <= amount; j++ {
              m[j] = min(m[j], m[j-coins[i]]+1)
          }
      }
      

状态机

  • 【309】最佳买卖股票时机含冷冻期

  • 【123】买卖股票的最佳时机 III:只能买卖两次

  • 【188】买卖股票的最佳时机 IV:

    • 集合&属性:dp[j][0]为最多买卖j次且最后一次是买入状态、dp[j][1]为最多买卖j次且最后一次是卖出状态

      • 初始化dp[j][0]=-price[0]
    • 状态计算:枚举price、j:

      • dp[j][0] = max(dp[j][0],dp[j-1][1]-prices[i])

      • dp[j][1] = max(dp[j][1],dp[j][0]+prices[i])

区间dp

  • 【516】最长回文子序列

    • 集合&属性:dp[i][j]为i~j的最长回文子序列长度,初始dp[i][i]=1

    • 状态计算:枚举k、i

      • 不选最前、最后的字母:dp[j][i+j] = max(dp[j+1][i+j], dp[j][i+j-1])
      • 如果s[i]==s[i+k]:dp[i][i+k] = dp[i+1][i+k-1]+2
  • 【647】回文子串:标准区间dp

    • 集合 & 属性:dp[i][j]为s[i:j+1]是否为回文串

    • 状态计算:初始化长度为1的为true

      • 从2开始枚举区间,再枚举起点,如果左右字母相同dp[i][j] = dp[i+1][j-1]