【DP-01】动态规划算法原理介绍
2021-01-14 15:13
标签:src http 解决 递归 distrib 避免 记录 lib ogr 把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划 --百度定义
动态规划算法(Dynamic Programming-DP)是通过拆分问题,定义问题状态和状态之间的关系,将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策选取那些有可能达到最优的局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
已知问题规模为n的前提A,求解一个未知解B。(我们用An表示"问题规模为n的已知条件")
此时,如果把问题规模降到0,即已知A0,可以得到A0->B.
1)如果从A0添加一个元素,得到A1的变化过程。即A0->A1; 进而有A1->A2; A2->A3; …… ; Ai->Ai+1. 这就是严格的归纳推理,也就是我们经常使用的数学归纳法;
对于Ai+1,只需要它的上一个状态Ai即可完成整个推理过程(而不需要更前序的状态)。我们将这一模型称为马尔科夫模型。对应的推理过程叫做"贪心法"。
2)然而,Ai与Ai+1往往不是互为充要条件,随着i的增加,有价值的前提信息越来越少,我们无法仅仅通过上一个状态得到下一个状态,因此可以采用如下方案:
{A1->A2}; {A1, A2->A3}; {A1,A2,A3->A4};……; {A1,A2,...,Ai}->Ai+1. 这种方式就是第二数学归纳法。
对于Ai+1需要前面的所有前序状态(或几个状态)才能完成推理过程。我们将这一模型称为高阶马尔科夫模型。对应的推理过程叫做"动态规划法"。
这个步骤可能有点枯燥,建议结合后面例题进行阅读。
子问题是和原问题相似,但规模较小的问题。第一步就是缩小规模,利用小规模子问题刻画其结构特征。
说明:创建一个一维数组或者二维数组,保存每一个子问题的结果,具体创建一维数组还是二维数组看题目而定
注意:子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。
找出状态转换方程,也就是说找到每个状态跟他上一个状态的关系,根据状态转化方程写出代码。
在确定了子问题的递推关系之后,下一步就是依次计算出这些子问题了。一般地,动态规划有两种计算顺序:
不过在普通的动态规划题目中,99% 的情况我们都不需要用到备忘录方法,即自底向上的 dp 数组。
目的:空间复杂度的较小。
在很多问题中,DP数组并非全部用上,很多情况下只使用一小部分或空间可重复利用,这给空间压缩带来可能。
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。
每个房子的金额用H表示,那么k个房子有两种偷发,如上图所示。容易得出递推公式(重要):
注意这里涉及到边界值:
那么,既然 DP 数组中的依赖关系都是向右指的,DP 数组的计算顺序就是从左向右。这样我们可以保证,计算一个子问题的时候,它所依赖的那些子问题已经计算出来了。
确定了 DP 数组的计算顺序之后,我们就可以写出题解代码了:见3.3的代码一。
最后一步计算 f(n) 的时候,实际上只用到了 f(n-1)和 f(n-2) 的结果。那么只用两个变量保存两个子问题的结果,就可以依次计算出所有的子问题。下面的图比较了空间优化前和优化后的对比关系,代码见3.3代码二:
代码一:未进行优化的DP
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
# 子问题:
# f(k) = 偷 [0..k) 房间中的最大金额
# f(0) = 0
# f(1) = nums[0]
# f(k) = max{ rob(k-1), nums[k-1] + rob(k-2) }
N = len(nums)
dp = [0] * (N+1)
dp[0] = 0
dp[1] = nums[0]
for k in range(2, N+1):
dp[k] = max(dp[k-1], nums[k-1] + dp[k-2])
return dp[N]
代码二:优化后的DP
def rob(self, nums: List[int]) -> int:
prev = 0
curr = 0
# 每次循环,计算"偷到当前房子为止的最大金额"
for i in nums:
# 循环开始时,curr 表示 dp[k-1],prev 表示 dp[k-2]
# dp[k] = max{ dp[k-1], dp[k-2] + i }
prev, curr = curr, max(curr, prev + i)
# 循环结束时,curr 表示 dp[k],prev 表示 dp[k-1]
return curr
动态规划算法:它通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
分治法:若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。
分治算法不强调记录算过的数据,动态规划为了避免重复计算,一定会记录数据。
不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
【1】 六大算法之三:动态规划
【2】leetcode动态规划的解析 【DP-01】动态规划算法原理介绍 标签:src http 解决 递归 distrib 避免 记录 lib ogr 原文地址:https://www.cnblogs.com/yifanrensheng/p/12940852.html目录
一、定义
1.1 定义
1.2 能用动规解决的问题的特点
1.3 说明
二、动态规划的步骤
2.1 定义子问题
2.2 写出子问题的递推关系
2.3 确定 DP 数组的计算顺序
2.4 空间优化(可选)
三、例题分析 - leetcode198. 打家劫舍
3.1 问题
3.2 求解
1)步骤一:定义子问题
2)写出子问题的递推关系
3)确定 DP 数组的计算顺序
4 )空间优化(可选)
3.3 代码
四、算法对比
4.1 动态规划和分治区别
五、总结
参考文献: