动态规划理论基础
相关代码用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也非常重要,总的来说动态规划确定好思路还是很好解决问题的
文档信息
- 本文作者:Mikatsuki
- 本文链接:https://akitozz.github.io/2023/03/29/dp-1/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)