爬楼梯
- 题目
- 函数原型
- 边界判断
- 算法设计:递归
- 算法设计:递归+记忆化搜索
- 算法设计:动态规划
- 算法设计:递推
题目
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。
你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n
是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
函数原型
C的函数原型:
int climbStairs(int n){}
边界判断
int climbStairs(int n){
if( n < 0 )
return 0;
}
算法设计:递归
题目就是,计算【斐波纳妾数列】。
斐波纳妾数列的定义:
这是一个天然的递归表达式,可以很直观用递归表示出来。
递归是一个视角,即从后往前看,具体的介绍请看:《递归》。
/* 递归 */
int climbStairs(int n){
if( n == 0 || n == 1 )
// n == 0 是没有一阶台阶,也有一种可能,或者写为 n <= 2 return n;
return 1;
return climbStairs(n-1) + climbStairs(n-2);
}
递归的复杂度:
- 时间复杂度:
- 空间复杂度:
但提交上去,超出时间限制了。
这是为啥?
算法设计:递归+记忆化搜索
因为在递归计算【斐波纳妾数列】的过程中,有大量的重复计算。
第一个数有重复计算:
第二个也有重复计算:
为了避免重复计算,我们可以用个数组保存计算得到的值,当再次需要这个值时,只需要查找即可。
int memo[1024]; // 全局变量,每个元素默认为 0
/* 递归 + 记忆化搜索 */
int climbStairs(int n){
if( n == 0 || n == 1 )
return 1;
if( memo[n] == 0 ) // 没有计算过,就计算,并把结果存储到 memo[]
memo[n] = climbStairs(n-1) + climbStairs(n-2);
return memo[n]; // 计算过的话,直接返回
}
这种避免重复的计算方法,也称为【记忆化搜索】。
递归:从后往前看,使用记忆化搜索是因为递归里有大量的重复,使用可以避免重复计算。
递归+记忆化搜索的复杂度:
- 时间复杂度:
- 空间复杂度:
算法设计:动态规划
而动态规划就是采用递推的方法,从前往后解决问题,从解决小问题开始,再到整体的问题求解。
/* 动态规划 */
int climbStairs(int n){
int memo[n+1];
memset(memo, 0, sizeof(int) * n+1);
memo[0] = 1;
memo[1] = 1;
for( int i=2; i<=n; i++ )
memo[i] = memo[i-1] + memo[i-2];
return memo[n];
}
动态规划:将原问题拆解为若干个子问题,同时保存子问题的答案,使得每个子问题的只求解一次,最终获得原问题的答案。
回顾一下,我们是如何计算【斐波纳妾数列】的:
采用直观的递归算法,但因为递归有重复计算,所以引进记忆化搜索。
整个计算过程是自顶向下,从后往前推的,如果要达到第 阶,就先要到达第 阶 or 第 阶,要达到第
动态规划的复杂度:
- 时间复杂度:
- 空间复杂度:
算法设计:递推
递推,即从前往后看。
int climbStairs(int n){
if ( n < 0 ) return 0;
if (n <=2 ) return n;
int fn1 = 1, fn2 = 2;
int fn3;
for (int i = 2 ;i < n; i++){
fn3 = fn2 + fn1;
fn1 = fn2;
fn2 = fn3;
}
return fn3;
}
递推的复杂度:
- 时间复杂度:
- 空间复杂度: