DP
树形
-
【337】打家劫舍 III:dfs+返回最大、不偷此节点的收益
- 状态计算:
max(root.Val + l2 + r2,l1 + r1), l1 + r1
- 状态计算:
一维
-
斐波那契数列:
f[i]=f[i-1]+f[i-2]- 【70】爬楼梯
-
卡特兰数:
-
【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...次
-
-
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]
- 如果s1[i-1]==s3[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]
- 如果s[i]==t[j]:
-
-
【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]
- 从2开始枚举区间,再枚举起点,如果左右字母相同
-