参考博客:动态规划详解
1. 什么是动态规划
动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
1.1 重叠子问题与最优子结构
1.1.1 重叠子问题
重叠子问题是指在问题的求解过程中,存在多次使用相同的子问题的情况,即在求解问题的不同阶段,需要求解的子问题可能是相同的。重叠子问题是动态规划算法设计的基础之一,利用子问题的重叠性可以减少重复计算,提高算法效率。
1.1.2 最优子结构
最优子结构是指问题的最优解可以通过子问题的最优解来构造得到。也就是说,问题的最优解包含子问题的最优解。通俗地说,就是大问题的最优解可以由小问题的最优解推出。这是动态规划的关键性质之一。
1.1.3 举例
举个例子,假设有一个包含n个元素的序列,需要找出其中的最长递增子序列(LIS,Longest Increasing Subsequence)。这个问题就具有最优子结构性质。如果一个序列的LIS已知,那么如果在其末尾添加一个元素,就有两种情况:
- 如果该元素大于当前LIS的末尾元素,那么新序列的LIS为当前LIS加上这个元素,长度为原序列的LIS长度加1;
- 如果该元素小于等于当前LIS的末尾元素,那么当前LIS不会受到影响,新序列的LIS仍然是原序列的LIS。
因此,该问题的最优解可以通过已知的子问题的最优解构造得到。
1.2 动态规划的核心思想
动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。
我们来看下,网上比较流行的一个例子:
A : "1+1+1+1+1+1+1+1 =?"
A : "上面等式的值是多少"
B : 计算 "8"
A : 在上面等式的左边写上 "1+" 呢?
A : "此时等式的值为多少"
B : 很快得出答案 "9"
A : "你怎么这么快就知道答案了"
A : "只要在8的基础上加1就行了"
A : "所以你不用重新计算,因为你记住了第一个等式的值为8!动态规划算法也可以说是 '记住求过的解来节省时间'"
1.3 从青蛙跳台阶进入动态规划
leetcode原题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 10 级的台阶总共有多少种跳法
1.3.1 解题思路
基本思想:动态规划从较小问题的解,由交叠性质,逐步决策出较大问题的解,它是从f(1)往f(10)方向,往上推求解,所以称为自底向上的解法。
什么意思,我们从第一个台阶往上推,假设跳到第n级台阶的跳数我们定义为f(n):
- 当只有1级台阶时,只有一种跳法,即f(1)= 1;
- 当只有2级台阶时,有两种跳法。第一种是直接跳两级。第二种是先跳一级,然后再跳一级。即f(2) = 2;
- 当有3级台阶时,也有两种跳法。第一种是从第1级台阶直接跳两级。第二种是从第2级台阶跳一级。即f(3) = f(1) + f(2);
- 要想跳到第4级台阶,要么是先跳到第3级,然后再跳1级台阶上去;要么是先跳到第2级,然后一次迈2级台阶上去。即f(4) = f(2) + f(3);
此时,我门就能得到公式:
f(1) = 1;
f(2) = 2;
f(3) = f(1) + f(2);
f(4) = f(2) + f(3);
…
f(10) = f(8) + f(9);
即f(n) = f(n - 2) + f(n - 1)。
此时我们来看看动态规划的典型特征在此题中的展现:
- 最优子结构:f(n-1)和f(n-2) 称为 f(n) 的最优子结构。
- 重叠子问题:比如f(10)= f(9)+f(8),f(9) = f(8) + f(7) ,f(8)就是重叠子问题。
- 状态转移方程:f(n)= f(n-1)+f(n-2)就称为状态转移方程。
- 边界:f(1) = 1, f(2) = 2 就是边界。
1.3.2 代码
代码思路如图:
代码实现:
class Solution {
public:
int numWays(int n) {
int dp[101] = {
0};
int mod = 1000000007;
dp[0] = 1;
dp[1] = 1;
dp[2] =