动态规划_理论基础

2023/03/29 Dynamic Programming 算法 数据结构 共 1047 字,约 3 分钟
Mikatsuki

动态规划理论基础

相关代码用golang编写

什么是动态规划

动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。

所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的

动态规划五部曲

  • 确定dp数组及下标含义
  • 确定状态转移方程
  • 确定dp数组的初始化
  • 确定遍历顺序
  • 举例推导确定dp数组正确性

题例

这里以菲波拉契数为例,

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

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

这是一个很好理解的动态规划,因为递推公式已经直接告诉我们了:dp[i] = dp[i-1] + dp[i-2]

首先按照五部曲,我们先确定dp数组及下标含义,这里是求解F(n),那我们就定义dp数组,其中数组每一个数为斐波拉契数,即dp[i] = F(i)

第二步dp推导公式题目已经告诉我们,而dp数组的初始化我们也知道即dp[0] = 0, dp[1] = 1

接下来我们确认遍历顺序即可,这里是要我们求第n个斐波拉契数,那我们只需要将dp数组填充到长度n+1即可

不难写出以下代码

func fib(n int) int {
    if n < 2 {
        return n
    }

    dp := make([]int, n+1)
    dp[0] = 0
    dp[1] = 1

    for i := 2; i <= n; i++ {
        dp[i] = (dp[i-2] + dp[i-1])
    }

    return dp[n]
}

时间复杂度:O(n) 空间复杂度:O(n)

并且我们可以自己写一组菲波拉契数列来验证,当n=10时,数列为:0 1 1 2 3 5 8 13 21 34 55,我们可以在代码中打印出推导的dp数列来验证是否正确

当然,这里记录了整个推导数组,其实题目只让求第n个数,其中并不需要记录所有结果,但是这样对dp数组更好理解

递归解法

当然本题还可以用递归来处理

func fib(n int) int {
    if n < 2  {
        return n
    }

    return fib(n-2) + fib(n-1)
}

时间复杂度:O(2^n) 空间复杂度:O(n)

总结

动态规划五步曲的思考方式真的很重要,理解dp数组的含义,才能确定遍历顺序并有效的使用推导方程去推导整个dp数组,而dp数组初始化又类似于递归的返回点,是整个推导成立的基础,而论证dp数组的正确性对于debug也非常重要,总的来说动态规划确定好思路还是很好解决问题的

文档信息

Search

    Table of Contents