学习笔记:动态规划

动态规划

之前遇到动态规划的问题一直感觉头疼,正巧最近看了一些动态规划的内容,这里就简单聊聊动态规划

先简单聊一下关于动态规划的概念,这里引用百度百科的一句话:

动态规划算法通常用于求解具有某种最优性质的问题

翻译成人话就是:求个最优解,可以是最大值,最小值啥的。而求解的思路大体就是将原本的问题拆分成多个子问题,再分别求这些子问题的最优解。

这里再引用quora上一个高赞回答来解释动态规划,

1+1+1+1+1+1+1+1 等于多少?
8!

那么在上面那个式子后面再+1等于多少?
9!

你几乎不需要任何犹豫,可以脱口而出地说出答案。这是因为你已经知道之前的一串+1最后的结果是8,现在只需要再+1就可以得到最终答案。

可以看到,在这个例子中将整个问题拆分成了前面8个1相加以及最后再加1两个子问题,当你知道前面8个1相加的结果时,你可以很容易得到最后的答案。

因此,动态规划本质上来说就是将一个大问题抽丝剥茧,庖丁解牛式地拆分成一个个子问题,再依次解决这些子问题从而得到最终的最优解

动态规划的三要素

聊完了什么是动态规划,现在来看看动态规划的三要素,动态规划的三要素包括:

1. 重叠子问题
2. 最优子结构
3. 状态转移方程

先来解释一下什么是重叠子问题,举个斐波那契数列的例子。
现在已知 :

f(1) = 1, f(2) = 1,
f(n) = f(n - 1) + f(n - 2) (n > 2)

给定一个n值,求f(n)

在这个例子中,要求f(n), 我们需要f(n-1) 以及f(n-2),因此,在求解f(n) 的时候可以将该问题转化成求f(n-1),以及f(n-2)两个子问题。一般来说,遇到这样的问题我们可以直接递归求解,代码可以写成这样:

def fib(self, n: int) -> int:
        if n == 0:
            return n
        if n <= 2:
            return 1
        return fib(n - 1) + fib(n - 2)

但这么写会有什么问题?

这么写会出现子问题重复计算的问题,比如,如果n = 80, 那么我们需要先计算f(79)以及f(78), 但再往下计算f(79)的时候又要计算f(78)和f(77),这里我们可以看到,f(78)被多次计算了,而这种重复计算子问题的情况在整个递归的过程中会多次发生。因此,在需要计算的所有子问题中出现重复的子问题的情况被称为重叠子问题。按照上面直接暴力递归的方式需要的时间复杂度为O(2n),随着n值的增大所耗费的时间会成指数增加。

而在该例子中,我们可以将f(n)看做一个状态,该状态的前两个状态分别为:f(n-1), f(n-2),对于任意大于2的n值,都有f(n) = f(n-1) + f(n-2)。因此,

f(n) = f(n-1) + f(n-2) (n > 2) 便是该问题的状态转移方程

那么最优子结构是什么意思?

先说结论,最优子结构中的子问题之间必须相互独立

举个简单的例子,小学的数学课中经常会遇到这样一类问题,一个班的同学出去郊游,给几种车型的价格以及载人上限,a型,b型,c型。每种车型租车数量不限,问如何租车最划算。在这个例子中,各个子问题之间相互独立,租a型车的数量不会影响到租b,c型车的数量,b,c型车的数量也不会影响到其他车型。因此,这种条件便满足最优子结构。

备忘录及hashmap优化

会到上面斐波那契数列的例子,我们已经知道了暴力递归会极大的影响运行效率,那么如何去优化呢?

在动态规划中,优化的方式无非两种,备忘录以及hashmap(也被称为DP Table),所谓的备忘录就是将已经计算过的子问题结果存储下来,在遇到重复的子问题时直接返回结果,这样可以极大的减少计算次数。

因此,优化后的代码可以写为:

class Solution:
    def fib(self, n: int) -> int:
        dic = {0 : 0, 1 : 1, 2 : 1}
        if n in dic:
            return dic[n]
        return self.recurrent(dic, n)

    def recurrent(self, dic, n):
        if n in dic:
            return dic[n]
        else:
            # 先完善备忘录,再返回最终结果
            dic[n] = self.recurrent(dic, n - 1) + self.recurrent(dic, n - 2)
        return dic[n]

而使用hashmap优化更加简单,之间从前往后遍历,从0到目标n之前的所有斐波那契数都算出来,算到n时直接返回即可,如此时间复杂度为O(n)。

这里就不贴代码了,非常简单。

写在最后

动态规划问题中最复杂的部分在于写出状态转移方程,对于任何动态规划问题而言,只要能写出状态转移方程,剩下的问题无非就是使用备忘录或者hashmap进行优化,减少计算次数来降低时间复杂度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值