这个台阶问题实际上是一个经典的动态规划入门题目。想象你站在一栋高楼的底部,面前有一段n级的台阶。每次你可以选择迈上1级、2级或3级台阶。我们需要计算出从底部到达顶部所有可能的走法总数。
这个问题看似简单,但蕴含着递归和动态规划的核心思想。让我们先从小规模案例入手:
通过观察这些基础案例,我们可以发现一个规律:到达第n级台阶的走法数,等于前三级台阶走法数的总和。这是因为最后一步只能是1级、2级或3级跨上来。
最直观的解法是使用递归。我们可以将问题分解为子问题:
c复制int countWays(int n) {
if (n == 0) return 1; // 地面算作一种"走法"
if (n == 1) return 1;
if (n == 2) return 2;
if (n == 3) return 4;
return countWays(n-1) + countWays(n-2) + countWays(n-3);
}
注意:这里n=0返回1是递归的基准情况,表示已经到达目标的一种有效走法。
虽然递归解法简单直观,但它存在严重的效率问题。让我们分析其时间复杂度:
例如计算countWays(20):
这种暴力递归在实际应用中是完全不可行的,我们需要更高效的解决方案。
动态规划(Dynamic Programming)通过存储子问题的解来避免重复计算。对于这个台阶问题:
让我们详细分析题目提供的C语言实现:
c复制#include <stdio.h>
int main(int argc, char **argv) {
int num;
int count = 0;
int a[36] = {0, 1, 2, 4}; // 初始化基础情况
scanf("%d", &num);
for (int i = 4; i <= num; i++) {
a[i] = a[i - 1] + a[i - 2] + a[i - 3];
}
printf("%d\n", a[num]);
return 0;
}
关键点说明:
我们可以进一步优化空间复杂度到O(1),因为每个状态只依赖前三个状态:
c复制int countWaysOptimized(int n) {
if (n == 0 || n == 1) return 1;
if (n == 2) return 2;
if (n == 3) return 4;
int a = 1, b = 2, c = 4, d;
for (int i = 4; i <= n; i++) {
d = a + b + c;
a = b;
b = c;
c = d;
}
return c;
}
这个版本只使用4个变量轮流存储中间结果,大大节省了内存空间。
这个问题实际上是一个三阶线性递推关系,其特征方程为:
x³ = x² + x + 1
解这个方程可以得到三个特征根,进而得到问题的通解公式。不过对于编程实现而言,动态规划解法已经足够高效。
这个基础问题可以衍生出许多有趣的变种:
步长变化:如果允许的步长变为[1,3,5]会怎样?
带限制条件:某些台阶不能踩(如隔层有钉子)
最小步数问题:求到达顶部的最少步数
在实际编码中,需要特别注意边界条件:
改进后的健壮版本:
c复制#include <stdio.h>
#include <stdlib.h>
#define MAX_STAIRS 1000
int main() {
int num;
printf("请输入台阶数(0-%d): ", MAX_STAIRS);
if (scanf("%d", &num) != 1 || num < 0 || num > MAX_STAIRS) {
printf("输入无效\n");
return 1;
}
long long dp[MAX_STAIRS + 3] = {1, 1, 2, 4};
for (int i = 4; i <= num; i++) {
dp[i] = dp[i-1] + dp[i-2] + dp[i-3];
}
printf("走法总数: %lld\n", dp[num]);
return 0;
}
完善的测试应该包括:
基础案例:
中等规模:
边界情况:
让我们比较不同实现的性能(在n=30时):
显然,动态规划方案在效率上具有绝对优势。
对于非常大的n(如n=1e18),我们可以使用矩阵快速幂将时间复杂度降至O(log n):
虽然实现更复杂,但对于超大规模问题这是必要的。
由于动态规划的顺序依赖性,这个特定问题难以并行化。但对于一些变种问题(如允许任意步长),可能存在并行计算的机会。
虽然我们主要讨论C实现,但了解其他语言的写法也很有帮助:
Python示例(带记忆化递归):
python复制from functools import lru_cache
@lru_cache(maxsize=None)
def count_ways(n):
if n < 0: return 0
if n == 0: return 1
return count_ways(n-1) + count_ways(n-2) + count_ways(n-3)
Java示例(迭代DP):
java复制public int countWays(int n) {
if (n <= 1) return 1;
int[] dp = new int[n+1];
dp[0] = 1; dp[1] = 1; dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2] + dp[i-3];
}
return dp[n];
}
这类问题在实际工程中有诸多应用场景:
理解这类基础问题的解法,有助于我们解决更复杂的实际问题。