动态规划详解

动态规划的入门,一般是从斐波拉契数列开始。该数列由0和1开始,后面的每一项数字都是前面两项数字的和,定义如下:

F(0) = 0,   F(1) = 1
F(n) = F(n - 1) + F(n - 2), 其中 n > 1

给定一个n,要求F(n),用递归方法可以很容易地解决这个问题,代码如下:

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

然而,上面的递归方法会造成大量重复计算,因为有很多重复子问题,时间复杂度为O(2^n)。例如,在求f(5)时,需要先求子问题f(4)和f(3)。递归地求f(4)时,又要先求子问题f(3)和f(2),这里的f(3)与求f(5)时的子问题就重复了。

为了解决这个问题,我们就想到一个方法:如果我们每次都把结果保存下来,复杂度就会大大降低。能不能让每个重复的子问题都只计算一次,即每个F(n)都只计算一次。这就是动态规划的核心思想:

  1. 将原问题分解成一系列子问题
  2. 每个子问题只求解一次,保存到一个状态数组dp[]

用动态规划来解斐波拉契数列:

def fib(self, n: int) -> int:
    if n == 0 or n == 1:
        return n
    dp = [0] * (n+1)
    dp[0] = 0
    dp[1] = 1
    for i in range(2, n+1):    
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

将时间复杂度由O(2^n)降低到了O(n),真香!


如果看了上面的内容,你还是云里雾里,那么:

很正常

理解了dp的思想,也不一定会刷题,下面分享一套自己的刷题模板。首先来看一个经典的爬楼梯问题:

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

前面说了动态规划的核心思想,是把一个问题分解成许多个不互相重合的子问题。对于这个爬楼梯问题,我们需要怎么开始分解呢?

一个很好的方法是,先找到最后一步的解决方法,即得到最终答案的前一步。对于这个例子,最后一步爬完楼梯之后会到达顶楼。那么要到达顶楼、最后一步应该是什么呢?

很显然,由于每次只能爬一个或者两个台阶,那么到达顶楼之前,要么在倒数第一层,最后爬一个台阶登顶。要么在倒数第二层,最后爬两个台阶登顶。这样问题就分解成了:

到达顶楼的方法 = 到达倒数第一层的方法 + 到达倒数第二层的方法

这样就可以自顶向下递推,用dp[i]来表示到达第i层的方法,写成伪代码:

    dp[i] = dp[i-1] + dp[i-2]

这里再回顾一下前面,为什么递归的时间复杂度非常爆炸?

因为计算dp[i]的时候需要计算一次dp[i-2],计算dp[i-1]的时候,由于dp[i-1] = dp[i-2] + dp[i-3],也需要计算一次dp[i-2],这里就重复了,学过编程的人都知道,递归里的重复是非常恐怖的。

回过来,dp[]数组叫做状态数组,目的是保存之前每一次计算的值。既然计算后面的值需要用到前面的值,那么就肯定是从前面开始计算,即自底向上。

到这里,大家都应该发现了,我们将问题从递归的自顶向下、变成了动态规划的自底向上,从而降低了复杂度。即:

 递归       ->      动态规划
  ||                  ||
自顶向下     ->      自底向上

那么爬楼梯问题的的动态规划解应该为:

def climbStairs(self, n: int) -> int:
    if n == 1:
        return 1
    dp = [0]*(n+1)
    dp[0] = 0
    dp[1] = 1
    dp[2] = 2
    for i in range(3, n+1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值