前言
今天来看一下动态规划的题目,动态规划,也是经典算法的一种,更是面试常考的算法
究竟什么是动态规划算法呢,下面是DeepSeek选手给出的答案
定义
动态规划是一种 分阶段求解最优化问题 的算法思想,它通过 拆分问题、存储子问题的解(避免重复计算),最终递推得到全局最优解。
核心思想
-
分治思想:将大问题分解为相互重叠的子问题。
-
记忆化存储:用数组(
dp
表)记录子问题的解,避免重复计算。 -
递推求解:通过状态转移方程,从小问题逐步推导大问题的解。
适用场景
动态规划适用于具有以下特征的问题:
-
最优子结构:问题的最优解包含子问题的最优解。
(例如:最短路径的子路径也是最短的) -
重叠子问题:子问题被重复计算多次。
(例如:斐波那契数列中fib(5)
依赖fib(4)
和fib(3)
,而fib(4)
又依赖fib(3)
)
今天我要向大家介绍的是做动态规划算法的基本五步(这里是借鉴b站上讲算法的大佬——代码随想录的想法,大家有空可以去看看人家的算法视频),先通过简单题理解如何运用五步法
到了后面的延申题就不至于毫无思路了
确定dp数组(dp table)以及下标的含义
确定递推公式
dp数组如何初始化
确定遍历顺序
举例推导dp数组
上面的dp数组也算是动态规划里的核心内容吧,就是用他来存储子问题的解
例题
斐波那契数
说了那么多还不如先看看具体题目,咱们具体题目具体分析
原来斐波那契数列也属于动态规划算法,我当初是用递归做的,也没想太多。
递归代码虽然简单,但是复杂度却不尽人意;
首先来看看上面的题,题意是求第n个斐波那契数
首先来看看什么是斐波那契数;
就是当n=1,2时,对应的值都是1,当n>2时,f(n)=f(n-1)+f(n-2)
所以会有
1 1 2 3 5 8 13 21
这些数就是斐波那契数
然后再看看上面的五步法
首先来看看第一步,确定dp数组以及下标的含义
这里求的是斐波那契数,那么dp数组自然是存放斐波那契数的,那么dp[i]就表示为第i个斐波那契数吧;
然后第二步,确定递推公式,其实也就是找规律,看看题目要求的数字有什么规律,这里题目已经给出了,当n>2时,满足f(n)=f(n-1)+f(n-2) 剩下的部分再单独处理
接着是第三步,dp数组如何初始化,那么我们知道,dp[i]表示的是第i个斐波那契数,dp[1]=1,dp[2]=1,那么dp[0]呢?
或许应该等于0,这样刚好就与性质对应上了,逻辑也说得通了
第四步,确定遍历顺序,因为动态规划有些题目会存在从后往前遍历的问题,所以这里也要考究一下遍历顺序的问题,这道题比较基础,值也是从小到大的,所以遍历顺序也是从小到大
第五步举例推导一下,这里我们写个伪代码,试试推导一下逻辑能否成立
vector<int> dp(n+1);
dp[0]=0;
dp[1]=1;
for(int i=2;i<n;i++)//由于0,1都已经做处理了,所以遍历从2开始
{
dp[i]=dp[i-1]+dp[i-2]
}
假设n等于5
dp[3]=2,dp[4]=3,dp[5]=5
似乎和刚刚的手动推导的一样,那我们就把想法变成代码,试试能不能通过
这里正常通过了
时间复杂度是O(n),似乎还能再优化一下,感觉只维护两个值也行?
这里也是成功的把代码的时间复杂度降到了O(1)
跳台阶
想当初第一次看见这道题,竟毫无思路,现在才知道要先分析一下数据
我们先来看看,这似乎也是斐波那契数列
跳的台阶数 跳法
1 1
2 2
3 3
4 5
5 8
那就简单了 dp数组存放的是跳台阶时可能有的跳法,dp[i]则表示的是第i层台阶总共有的跳法
递推公式也简单 满足f(n)=f(n-1)+f(n-2)就行
初始化的话,由于不存在第0层,直接考虑第一层和第二层
dp[1]=1
dp[2]=2
遍历顺序也是从小到大
最后写个伪代码,简单推导一下
vector<int> dp(n+1)
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2]
}
假如是3层台阶
跳一遍一层,跳一遍两层
跳三遍一层
跳一遍两层,跳一遍一层
刚好是三次,现在验证一下思路
刚好过了
还可以和上面一样,优化一下思路