动态规划总结(多示例+讲解)
动态规划功能强大,它能够解决子问题并使用这些答案来解决大问题。但 仅当每个子问题都是离散的,即不依赖于其他子问题时,动态规划才管用。 比如,想去以下地方旅游4天,假设将埃菲尔铁塔加入“背包”后,卢浮宫将 更“便宜”:只要1天时间,而不是1.5天。用动态规划对这种情况建模呢? 这是没办法建模的,因为存在依赖关系。
景点 | 停留天数 | 评分 |
---|---|---|
埃菲尔铁塔 | 1.5天 | 8 |
卢浮宫 | 1.5天 | 9 |
巴黎圣母院 | 1.5天 | 7 |
一、斐波那契数列求解
题目:
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
😂
递归法
-
原理: 把f(n)问题的计算拆分成 f(n-1)和f(n−2)两个子问题的计算,并递归,以f(0)和f(1)为终止条件。
-
缺点: 大量重复的递归计算,例如f(n)和f(n - 1)两者向下递归需要 各自计算f(n−2)的值。
|
|
记忆化递归法
-
原理: 在递归法的基础上,新建一个长度为 n 的数组,用于在递归时存储 f(0) 至 f(n) 的 数字值,重复遇到某数字则直接从数组取用,避免了重复的递归计算。
-
缺点: 记忆化存储需要使用 O(N) 的额外空间。
|
|
动态规划法
- 原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)为转移方程。从计算效率、空间复杂度上看,动态规划是本题的最佳解法。
- 状态定义: 设 dp 为一维数组,其中 dp[i] 的值代表 斐波那契数列第 i 个数字 。
- 转移方程: dp[i + 1] = dp[i] + dp[i - 1] ,即对应数列定义 f(n + 1) = f(n) + f(n - 1);
- 初始状态: dp[0] = 0, dp[1]=1 ,即初始化前两个数字;
- 返回值: dp[n],即斐波那契数列的第 n 个数字。
|
|
二、把数字翻译成字符串
题目:
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有 多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例1:
输入: 12258 输出: 5 解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”
动态规划法
- 状态定义: 状态定义: 设动态规划列表 dp,dp[i] 代表以 $x_i$ 为结尾的数字的翻译方案数量
- 转移方程: 若 $x_i$和$x_{i-1}$组成的两位数字可被整体翻译,则 dp[i] = dp[i - 1] + dp[i - 2],否则 dp[i] = dp[i - 1]。 $$ dp[i] = \begin{cases} dp[i-1]+dp[i-2], & (10x_{i-1} + x_{i-1})\in[10,25] \\ dp[i-1], & (10x_{i-1} + x_i)\in[0,10)\bigcup(25,99] \end{cases} $$
- 初始状态: dp[0] = dp[1] = 1,即 “无数字” 和 “第1位数字” 的翻译方法数量均为 1;
- 返回值: dp[n],即此数字的翻译方案数量;
第一种写法:
|
|
第二种写法:
|
|
三、青蛙跳台阶问题求解
题目:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
动态规划法
- 状态定义: 设 dp 为一维数组,其中 dp[i] 的值代表斐波那契数列的第 i 个数字。
- 转移方程: dp[i + 1] = dp[i] + dp[i - 1],即对应数列定义 f(n + 1) = f(n) + f(n - 1);
- 初始状态: dp[0] = 1, dp[1]=1 ,即初始化前两个数字;
- 返回值: dp[n],即斐波那契数列的第 n 个数字。
|
|
四、连续子数组的最大和
题目:
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
动态规划法
- 原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)为转移方程。从计算效率、空间复杂度上看,动态规划是本题的最佳解法。
- 状态定义: 设 dp 为一维数组,其中 dp[i] 的值代表 斐波那契数列第 i 个数字 。
- 转移方程: dp[i + 1] = dp[i] + dp[i - 1],即对应数列定义f(n+1)=f(n)+f(n−1) ;
- 初始状态: dp[0] = 0, dp[1]=1,即初始化前两个数字;
- 返回值: dp[n] ,即斐波那契数列的第 n 个数字。
|
|
|
|
五、放苹果
题目:
把m个同样的苹果放在n个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
数据范围:0<=m<=10,1<=n<=10。
示例1:
输入: 7 3 输出: 8
动态规划法
- 原理:
递推的方式,利用公式来填表。将m个苹果放入n个盘子里,包含了2个事件:至少有一个盘子空着的事件A,
和所有盘子都不空的事件B(每个盘子都至少有一个苹果)。A∪B即所有情况。A就是求f(m, n-1),B就是f(m-n, n)。
事件B表示每个盘子都有一个苹果时再放m-n个苹果,等价于每个盘子都没有苹果时放m-n个苹果,所以可以直接写成
f(m-n, n)。注意m-n可能为负数,此时要返回0。例如,f(4,4)=f(4,3)+f(0,4),f(0,4)等于1,表示在4个盘子中各放1个苹果。
- 状态定义: 设 f[m][n] 为二维数组。
- 转移方程: f(m, n)=f(m, n-1)+f(m-n, n);
- 初始状态: f[0][1] = 0, f[1][1]=1,即初始化前两个数字, 注意数据范围;
- 返回值: f[m][n]。
|
|
递归法
|
|
总结: 由放苹果的示例可知,递归法和动态规划法相似,但是,当数据量较大的时候,递归法会进行大量的重复计算,效率降低。 此时就需要使用动态规划法。 总的来看,使用的公式是一样的,递归法使用函数存储和计算数据,而动态规划法是使用数组存储和 计算数据。