动态规划基础阶段之爬楼梯和杨辉三角
- 前言
- 二、爬楼梯
- 2.1、思路
- 2.2、代码实现
- 三、杨辉三角
- 3.1、思路
- 3.2、代码实现
- 四、杨辉三角2(进阶)
- 总结
前言
动态规划(Dynamic Programming,简称 DP)是一种解决多阶段决策过程最优化问题的方法。它是一种将复杂问题分解成重叠子问题的策略,通过维护每个子问题的最优解来推导出问题的最优解。
动态规划的主要思想是利用已求解的子问题的最优解来推导出更大问题的最优解,从而避免了重复计算。因此,动态规划通常采用自底向上的方式进行求解,先求解出小规模的问题,然后逐步推导出更大规模的问题,直到求解出整个问题的最优解。
动态规划通常包括以下几个基本步骤:
- 定义状态:将问题划分为若干个子问题,并定义状态表示子问题的解;
- 定义状态转移方程:根据子问题之间的关系,设计状态转移方程,即如何从已知状态推导出未知状态的计算过程;
- 确定初始状态:定义最小的子问题的解;
- 自底向上求解:按照状态转移方程,计算出所有状态的最优解;
- 根据最优解构造问题的解。
动态规划可以解决许多实际问题,例如最短路径问题、背包问题、最长公共子序列问题、编辑距离问题等。同时,动态规划也是许多其他算法的核心思想,例如分治算法、贪心算法等。
动态规划是一种解决多阶段决策过程最优化问题的方法,它将复杂问题分解成重叠子问题,通过维护每个子问题的最优解来推导出问题的最优解。动态规划包括定义状态、设计状态转移方程、确定初始状态、自底向上求解和构造问题解等步骤。动态规划可以解决许多实际问题,也是其他算法的核心思想之一。
二、爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
来源:力扣(LeetCode)。
2.1、思路
本问题其实常规解法可以分成多个子问题,爬第n阶楼梯的方法数量,等于 2 部分之和:
- 爬上n−1 阶楼梯的方法数量。因为再爬1阶就能到第n阶。
- 爬上n−2 阶楼梯的方法数量,因为再爬2阶就能到第n阶。
所以我们得到公式 :
dp[n]=dp[n−1]+dp[n−2]
同时需要初始化 dp[0]=1 和 dp[1]=1。
时间复杂度:O(n)。
2.2、代码实现
class Solution {
public:
int climbStairs(int n) {
if(n<2)
return 1;
int *dp=new int[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
上述代码动态分配一个数组,造成算法的空间复杂度为O(N),由于每次只要最近的两次结果,我们可以只保存最近的两次结果,而不保存所有的结果,即滚动数组。优化代码如下:
class Solution {
public:
int climbStairs(int n) {
int pre1=1,pre2=1,cur=1;
for(int i=2;i<=n;i++)
{
cur=pre1+pre2;
pre1=pre2;
pre2=cur;
}
return cur;
}
};
三、杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:
输入: numRows = 1
输出: [[1]]
来源:力扣(LeetCode)。
3.1、思路
从第三行开始,除了开始和末尾的位置上的元素,其余位置上的元素都是由上方的元素以及上方左侧的元素相加得到的,此时就很容易的到从第三行开始状态转移方程为dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1],之后就可轻松求解。
3.2、代码实现
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> res(numRows);
for(int i=0;i<numRows;i++)
{
res[i].resize(i+1);
res[i][0]=res[i][i]=1;
for(int j=1;j<i;j++)
{
res[i][j]=res[i-1][j-1]+res[i-1][j];
}
}
return res;
}
};
时间复杂度: O ( n u m R o w s 2 ) O(numRows^2) O(numRows2)。
空间复杂度:O(1);不考虑返回值的空间占用。
四、杨辉三角2(进阶)
给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: rowIndex = 3
输出: [1,3,3,1]
示例 2:
输入: rowIndex = 0
输出: [1]
示例 3:
输入: rowIndex = 1
输出: [1,1]
来源:力扣(LeetCode)。
思路:递推的方式,保存杨辉三角的所有结果,只返回需要的那行数组。
代码实现:
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<vector<int>> res(rowIndex+1);
for(int i=0;i<=rowIndex;i++)
{
res[i].resize(i+1);
res[i][0]=res[i][i]=1;
for(int j=1;j<i;j++)
{
res[i][j]=res[i-1][j-1]+res[i-1][j];
}
}
return res[rowIndex];
}
};
注意到对第 i+1 行的计算仅用到了第 i 行的数据,因此可以使用滚动数组的思想优化空间复杂度。
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<int> pre, cur;
for (int i = 0; i <= rowIndex; ++i) {
cur.resize(i + 1);
cur[0] = cur[i] = 1;
for (int j = 1; j < i; ++j) {
cur[j] = pre[j - 1] + pre[j];
}
pre = cur;
}
return pre;
}
};
时间复杂度: O ( r o w I n d e x 2 ) O(rowIndex^2) O(rowIndex2)。
空间复杂度:O(1);不考虑返回值的空间占用。
总结
动态规划(Dynamic Programming)是一种解决多阶段决策最优化问题的方法,它将复杂问题分解成重叠子问题并通过维护每个子问题的最优解来推导出问题的最优解。动态规划可以解决许多实际问题,例如最短路径问题、背包问题、最长公共子序列问题、编辑距离问题等。
动态规划的基本思想是利用已求解的子问题的最优解来推导出更大问题的最优解,从而避免了重复计算。它通常采用自底向上的方式进行求解,先求解出小规模的问题,然后逐步推导出更大规模的问题,直到求解出整个问题的最优解。
动态规划通常包括以下几个基本步骤:
- 定义状态:将问题划分为若干个子问题,并定义状态表示子问题的解;
- 定义状态转移方程:根据子问题之间的关系,设计状态转移方程,即如何从已知状态推导出未知状态的计算过程;
- 确定初始状态:定义最小的子问题的解;
- 自底向上求解:按照状态转移方程,计算出所有状态的最优解;
- 根据最优解构造问题的解。
动态规划的时间复杂度通常为 O ( n 2 ) O(n^2) O(n2)或 O ( n 3 ) O(n^3) O(n3),空间复杂度为O(n),其中n表示问题规模。在实际应用中,为了减少空间复杂度,通常可以使用滚动数组等技巧来优化动态规划算法。