这个问题描述的是:一只青蛙每次可以跳上1级或2级台阶,求跳上n级台阶共有多少种跳法。通过观察小规模案例,我们可以建立如下对应关系:
这个数列呈现斐波那契数列的特征,即f(n) = f(n-1) + f(n-2)。不同之处在于初始条件:斐波那契数列通常定义为f(1)=1, f(2)=1,而这里的跳台阶问题是f(1)=1, f(2)=2。
提示:这类问题属于典型的动态规划问题,其核心思想是将大问题分解为相互关联的子问题,通过存储和重用子问题的解来提高效率。
原始代码使用了尾递归形式,这是一种特殊的递归形式,其特点是递归调用是函数执行的最后一步操作。尾递归的优势在于可以被编译器优化为迭代形式,避免栈空间过度消耗。
c复制int way(int n, int last_last, int last) {
if (n >= 3)
return way(n - 1, last, (last + last_last));
else if (n == 1)
return last_last;
else if (n == 2)
return last;
else {
printf("输入错误,请重新输入\n");
find = 0;
}
}
对于n较大的情况(如n>40),纯递归解法效率会急剧下降。这时可以考虑以下优化方案:
代码中已经考虑了n为负数的异常情况,但实际应用中还需要考虑:
改进后的输入检查可以这样写:
c复制if(n <= 0) {
printf("台阶数必须为正整数\n");
return -1;
}
汉诺塔问题包含三个柱子和n个大小不一的盘子,要求将所有盘子从起始柱移动到目标柱,且:
移动次数遵循2ⁿ-1的规律,这可以通过数学归纳法证明:
核心递归函数如下:
c复制void Hanoi(int n, char pos1, char pos2, char pos3) {
if (n == 1) {
move(pos1, pos3);
} else {
Hanoi(n - 1, pos1, pos3, pos2);
move(pos1, pos3);
Hanoi(n - 1, pos2, pos1, pos3);
}
}
这个实现体现了分治思想:
虽然递归解法直观,但也可以使用栈来模拟递归过程:
c复制typedef struct {
int n;
char from, via, to;
} Task;
void HanoiIterative(int n, char from, char via, char to) {
Stack s;
initStack(&s);
push(&s, (Task){n, from, via, to});
while (!isEmpty(&s)) {
Task t = pop(&s);
if (t.n == 1) {
move(t.from, t.to);
} else {
push(&s, (Task){t.n-1, t.via, t.from, t.to});
push(&s, (Task){1, t.from, t.via, t.to});
push(&s, (Task){t.n-1, t.from, t.to, t.via});
}
}
}
栈溢出:递归深度过大时发生
重复计算:同一子问题多次求解
逻辑错误:递归条件不正确
如果青蛙可以跳1级、2级...m级台阶,解法如何变化?
此时递推公式变为:
f(n) = f(n-1) + f(n-2) + ... + f(n-m)
可以用动态规划实现:
c复制int jump(int n, int m) {
int dp[n+1];
dp[0] = 1;
for(int i=1; i<=n; i++) {
dp[i] = 0;
for(int j=1; j<=m && j<=i; j++) {
dp[i] += dp[i-j];
}
}
return dp[n];
}
非相邻移动限制:不能直接从A到C或C到A
多柱子问题:有4个或更多柱子时
环形排列:柱子排成环形
在实际编程练习中,建议从这些经典问题出发:
掌握递归思维需要时间和实践,关键是要理解问题分解的思路,而不是纠结于每一步的具体执行过程。正如计算机科学家Edsger Dijkstra所说:"递归的核心在于认识到问题可以被分解为相似的更小问题。"