Problem: 70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
文章目录
整体思路
这段代码同样旨在解决 "爬楼梯" 问题,但它采用的是一种 自底向上(Bottom-Up)的动态规划 方法,也称为 迭代法 或 制表法 (Tabulation)。与上一个自顶向下的记忆化搜索版本相比,这种方法通常更直接,并且没有递归带来的开销。
算法的核心逻辑是构建一个"DP table"(在此代码中是 f 数组),从最小的子问题开始,逐步推导出最终的解。
-
状态定义:
- 算法定义了一个数组
f,其中f[i]的含义是:爬到第i级台阶所拥有的不同方法数 。这个定义与记忆化搜索中的memo[i]完全相同。
- 算法定义了一个数组
-
基础情况 (Base Cases):
- 算法首先填充DP table的初始值,也就是最小的、已知的子问题的解。
f[0] = 1: 到达第 0 级台阶(起点)有 1 种方法(原地不动)。f[1] = 1: 到达第 1 级台阶有 1 种方法(从起点爬 1 阶)。- 这些基础情况是后续所有计算的基石。
-
状态转移 (State Transition):
- 算法使用一个
for循环,从i = 2开始,按顺序计算f[2],f[3], ..., 直到f[n]。 - 循环的每一步都应用了状态转移方程:
f[i] = f[i - 1] + f[i - 2]。 - 这个方程的逻辑是:要到达第
i级台阶,上一步必然是从第i-1级或第i-2级台阶上来的。由于f[i-1]和f[i-2]的值在计算f[i]时已经被计算出来并存储在数组中 ,我们可以直接使用它们来推导出当前状态f[i]的解。
- 算法使用一个
-
返回结果:
- 当循环结束后,
f数组的所有值都已被填充。我们需要的最终答案------爬n级台阶的方法数------就存储在f[n]中,直接返回即可。
- 当循环结束后,
这种自底向上的方法,通过迭代的方式,系统地解决了从最小到最大的所有子问题,最终得到原问题的解。
完整代码
java
class Solution {
/**
* 计算爬 n 级楼梯的总方法数。
* @param n 楼梯的总阶数
* @return 不同的方法总数
*/
public int climbStairs(int n) {
// 当 n <= 1 时,方法数就是 n 本身(爬0阶1种,爬1阶1种)。
// 虽然下面的代码也能处理,但这是一个常见的提前返回优化。
if (n <= 1) {
return 1;
}
// f: 动态规划数组,也称为 DP table。
// f[i] 用来存储爬到第 i 级台阶的方法数。
int[] f = new int[n + 1];
// 基础情况:
// 到达第0阶(起点)只有1种方法(原地不动)。
f[0] = 1;
// 到达第1阶只有1种方法(爬1个台阶)。
f[1] = 1;
// 从第2阶开始,迭代计算直到目标阶数 n。
for (int i = 2; i <= n; i++) {
// 状态转移方程:
// 到达第 i 阶的方法数 = 到达第 i-1 阶的方法数 + 到达第 i-2 阶的方法数。
// 因为 f[i-1] 和 f[i-2] 已经被计算过,可以直接使用。
f[i] = f[i - 1] + f[i - 2];
}
// 循环结束后,f[n] 中存储的就是爬 n 级楼梯的总方法数。
return f[n];
}
}
时空复杂度
时间复杂度:O(N)
- 循环 :算法的核心是一个
for循环,它从i = 2遍历到n。这个循环执行了n - 1次。 - 循环内部操作 :
- 在循环的每一次迭代中,执行的都是基本的数组访问和加法运算。
- 这些操作的时间复杂度都是 O(1)。
综合分析 :
算法由 N-1 次 O(1) 的操作组成。因此,总的时间复杂度是 (N-1) * O(1) = O(N)。
空间复杂度:O(N)
- 主要存储开销 :算法创建了一个名为
f的整型数组来存储动态规划的所有中间状态。 - 空间大小 :该数组的长度是
n + 1,其大小与输入n成线性关系。 - 其他变量 :
n,i等变量都只占用常数级别的空间,即 O(1)。
综合分析 :
算法所需的额外空间主要由 f 数组决定。因此,其空间复杂度为 O(N)。
空间优化提示 :
观察状态转移方程 f[i] = f[i - 1] + f[i - 2],可以发现 f[i] 的计算只依赖于前两个状态 f[i-1] 和 f[i-2]。因此,我们完全不需要存储整个 f 数组,只需用两个变量来滚动记录前两个状态即可。这样可以将空间复杂度优化到 O(1)。