2020-10-26动态规划

动态规划总结

一个简单的模板

for() //遍历状态(如函数的可变参数,数组的索引)
	if()	//选择
		dp[i] = dp[i-1];	//迭代公式(dp数组自底向上迭代,dp函数自顶向下递归)
	else
		...

维基百科中关于动态规划的描述是,只能运用于有最优子结构的问题,并且在处理的问题有很多重叠子问题时,特别有效。
最优子结构是指局部最优值能决定全局最优值。(这一点似乎和贪婪算法很相似)。
动态规划是一个求解最优化问题的方法,所以它的核心思想就是枚举,而确定状态迭代公式是动态规划问题的关键。

解法

  1. 暴力递归(递归树)
  2. 因为存在重叠子问题,所以进行剪枝(带备忘录的递归),即自顶向下;
  3. 通过dp数组(代替备忘录)的迭代,进行自底向上

滚动数组思想

一种常见的动态规划优化方法,如果当前状态只和前面某几个状态相关的时候,就可以考虑这种优化方法,目的是给空间复杂度「降维」。比如斐波那契数列,只需要前两个状态,那么就只需要两个变量即可。
用二维数组举例(来源.)

int i, j, d[100][100];
for(i = 1; i < 100; i++)
    for(j = 0; j < 100; j++)
        d[i][j] = d[i - 1][j] + d[i][j - 1];

上面的d[i][j]只依赖于d[i - 1][j], d[i][j - 1];
运用滚动数组

int i, j, d[2][100];
for(i = 1; i < 100; i++)
    for(j = 0; j < 100; j++)
        d[i % 2][j] = d[(i - 1) % 2][j] + d[i % 2][j - 1];   

题目类型

动态规划的题目分为两大类,一种是求最优解类,另一种就是计数类,它们都存在一定的递推性质(即最优子结构)。这两种情况都需要进行枚举

例1、正则表达式匹配

题解参考了英文leetcode中的题解

确定迭代公式
这道题关键点在于,* 匹配了多少次。它要么匹配0次,即dp[i][j] = dp[i][j-2],表示*和它前面的字符,都可以被忽略。要么它可以匹配多次。
(dp[i][j]为true,则表示s[0…i) 和p[0…j)能够匹配)

当匹配多次时,就在s字符串中不断去掉这样的重复字符,直到 * 只需要匹配0次。即dp[i][j] = dp[i-1][j] && (s[i-1] == p[j-2] || p[j-2] == ‘.’)。可知这种迭代需要保证两个字符相等。

完整的公式为

  1. p[j - 1] != '*'时,dp[i][j] = dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == ‘.’);
  2. p[j - 1] == '*'时,dp[i][j] = dp[i][j - 2] (*匹配0次)或者 dp[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == ‘.’);

值得注意的是,看着公式是从大到小递归迭代,理解起来有点困难。但是编写代码的时候,i 和 j 都是从小到大的。
比如对于aabb和ab,首先判断对于空串,ab的每一个从a开始的子串,能不能匹配。接着判断对于a,ab的每一个从a开始的子串,能不能匹配。然后是aa,…,最后就是aabb。

2、不同路径

3、买卖股票

参考链接121-123
分析这个问题,可知:最后一天时的利润,可以是前一天时的利润+(最后两天买入卖出的利润),括号里是可选的,因为它不一定存在利润。
难点在于如何定义出各种状态,以及状态转移公式。
dp[i][j] 表示到下标为 i 的这一天持股状态为 j (持有股票,还是持有现金)时,我们手上拥有的最大现金数。

4、背包问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值