动态规划—斐波那契系列
- 什么是动态规划
- 斐波那契数组相关题目
- 509. 斐波那契数 Easy
- 1137. 第 N 个泰波那契数 Easy
- 杨辉三角
- 118. 杨辉三角 Easy
- 爬楼梯相关题目
- 70. 爬楼梯 Easy
- 746. 使用最小花费爬楼梯 Easy
什么是动态规划
动态规划是一种通过将原问题分解为相对简单的子问题来解决复杂问题的方法。基本思想是递归地将一个复杂的问题划分为许多更简单的子问题,存储这些子问题的每个子问题的解,并最终将存储的答案用于解决原始问题。通过缓存子问题的解,动态规划有时可以避免指数级的浪费。它通常用于优化问题,其中需要找到最佳解决方案。动态规划算法通常用于解决具有重叠子问题和最优子结构性质的问题。
- 重叠子问题:原问题可以被分解为相同的子问题。这意味着在解决原问题时,我们可能多次解决相同的子问题。
- 最优子结构:问题的最优解可以通过其子问题的最优解来求解。
通常,使用动态规划解决问题的步骤包括以下几个方面:
- 定义子问题:将原问题分解为子问题。
- 解决子问题:解决子问题并将结果存储起来,通常使用数组或类似的数据结构来存储子问题的解,以避免重复计算。
- 合并子问题的解:利用子问题的解构建原问题的解。
- 递归或迭代:通常使用递归或迭代的方式来解决问题。
斐波那契数组相关题目
斐波那契数组是典型的动态规划问题,可以从存储的子问题答案扩展到要求解的大问题。
509. 斐波那契数 Easy
509. 斐波那契数
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
其实可以直接用一个数组,dp[i]就对应F(i),但实际上我们并不需要整个数组,只需要F(n),而F(n)只和F(n-1)和F(n-2)相关,因此直接用两个变量存储即可。更新过程就是F(n) = F(n - 1) + F(n - 2)。Java代码如下:
class Solution {
public int fib(int n) {
if(n <= 1)
return n;
int f0 =0, f1 = 1;
for(int i = 2; i <= n; i++){
int cur = f0 + f1;
f0 = f1;
f1 = cur;
}
return f1;
}
}
1137. 第 N 个泰波那契数 Easy
1137. 第 N 个泰波那契数
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
这个和上面的斐波那契基本一样,只是当前状态由前面三个状态决定,而不是前面两个。用三个变量分别存储Tn、Tn+1、Tn+2,按照 Tn+3 = Tn + Tn+1 + Tn+2逐步更新即可。 Java代码如下:
public int tribonacci(int n) {
if(n <= 1)
return n;
if (n == 2)
return 1;
int f0 = 0, f1 = 1, f2 = 1;
for(int i = 3; i <= n; i++){
int cur = f0 + f1 + f2;
f0 = f1;
f1 = f2;
f2 = cur;
}
return f2;
}
}
杨辉三角
118. 杨辉三角 Easy
118. 杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
其实斐波那契和杨辉三角,都是数学里面学过的,更新过程已经很清晰了。 上面的图这个三角形,如果变成数组左下角直角的形式,对于每一行,dp[i][j] = dp[i-1][j-1] + dp[i-1][j]。Java代码如下:
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> triangle = new ArrayList<>();
for(int i = 0; i < numRows; i++){
List<Integer> newRow = new ArrayList<>();
for(int j = 0; j <= i; j++){
if(j==0 || j==i)
newRow.add(1);
else
newRow.add(triangle.get(i-1).get(j) + triangle.get(i-1).get(j-1));
}
triangle.add(newRow);
}
return triangle;
}
}
爬楼梯相关题目
70. 爬楼梯 Easy
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
根据题目,到第n阶台阶,可以从n-1跨一步到;可以从n-2跨两步到。因此动态规划的状态转移:dp[n] = dp[n-1] + dp[n-2]。实际上只需要存储dp[n-1]和dp[n-2]这两个变量即可。Java代码如下:
class Solution {
public int climbStairs(int n) {
if(n <=2 )
return n;
int stair1 =1, stair2 = 2;
for(int i = 3; i <= n; i++){
int cur = stair1 + stair2;
stair1 = stair2;
stair2 = cur;
}
return stair2;
}
}
746. 使用最小花费爬楼梯 Easy
746. 使用最小花费爬楼梯
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
示例 1:
输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。支付 15 ,向上爬两个台阶,到达楼梯顶部。总花费为 15 。
上一个题目是直接求方法总和,这个题目是最小最大问题。 但本质上两个题一样的,因此到当前第i个台阶的代价,取决与i-1和i-2。因为在i-1个台阶上付出cost[i-1]的费用,选择向上一个台阶就到了第i阶;同样在第i-2个台阶上付出cost[i-2]的费用,选择向上两个台阶就到了第i个台阶。因为是最小值,因此dp[i] = min(dp[i-2] + cost[i-2], dp[i-1] + cost[i-1])
。由于只和dp[i-1]、dp[i-2]两个状态相关,因此可以优化成只用两个变量存储,Java代码如下:
class Solution {
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
if(n == 2)
return Math.min(cost[0],cost[1]);
int minCost1 = 0, minCost2 = 0;
for(int i = 2; i <= n ; i++){
int newCost = Math.min(minCost1 + cost[i-2], minCost2 + cost[i-1]);
minCost1 = minCost2;
minCost2 = newCost;
}
return minCost2;
}
}