1、描述
假设你正在爬楼梯,需要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 阶
2、算法
解法一:暴力法
思想:我们将会把所有可能爬的阶数进行组合,也就是 1 和 2 。而在每一步中我们都会继续调用climbStairs 这个函数模拟爬2 阶的情形,并返回两个函数的返回值之和。
climbStairs(i,n)=(i+1,n)+climbStairs(i+2,n)
其中 i 定义了当前阶数,而n 定义了目标阶数。
时间复杂度:O(2^n)
func climbStairs(_ n: Int) -> Int {
return climb_Stairs(0, n)
}
func climb_Stairs(_ i : Int, _ n : Int)-> Int{
if i > n {
return 0
}
if i == n {
return 1
}
return climb_Stairs(i+1, n)+climb_Stairs(i+2, n)
}
解法二:记忆化递归
思路:在暴力法中,我们计算每一步的结果时出现了冗余。另一种思路是,我们可以把每一步的结果存储在memo 数组之中,每当函数再次被调用,我们就直接从memo 数组返回结果。 在memo 数组的帮助下,我们得到了一个修复的递归树,其大小减少到n。
时间复杂度:O(n)
func climbStairs(_ n: Int) -> Int {
var memo : [Int] = [Int].init(repeating: 0, count: n+1)
return climb_Stairs1(0, n, &memo)
}
func climb_Stairs1(_ i : Int, _ n : Int, _ memo : inout [Int])-> Int{
if i > n {
return 0
}
if i == n {
return 1
}
if memo[i] > 0 {
return memo[i]
}
memo[i] = climb_Stairs1(i+1, n, &memo)+climb_Stairs1(i+2, n, &memo)
return memo[i]
}
解法三:动态规划
思想:
它的最优解可以从其子问题的最优解来有效的构建
第i阶可以由以下两种方法得到:
1)在第(i-1)阶后向上爬1阶
2)在第(i-2)阶后向上爬2阶
所以到达第i阶的方法总数就是到第(i-1)阶和第(i-2)阶的方法数之和
令dp[i]表示能到达第i阶的方法总数:dp[i] = dp[i-1]+dp[i-2]
时间复杂度:O(n)
func climbStairs(_ n: Int) -> Int {
if n==1 {
return 1
}
var dp : [Int] = [Int].init(repeating: 0, count: n+1)
dp[1] = 1
dp[2] = 2
var i = 3
while i<=n {
dp[i] = dp[i-1]+dp[i-2]
i += 1
}
return dp[n]
}
解法四:斐波那契数
思路:由动态规划式子dp[i] = dp[i-1]+dp[i-2]分析,可以很容易的得出dp[i]就是第i个斐波那契数
Fib(n) = Fib(n-1) + Fib(n-2)
我们必须以找出以1和2作为第一项和第二项的斐波那契数列中的第n个数,也就是说Fib(1) = 1,Fib(2) = 2
时间复杂度:O(n)
func climbStairs(_ n: Int) -> Int {
if n==1 {
return 1
}
var first = 1
var second = 2
var i = 3
while i<=n {
var third = first+second
first = second
second = third
i += 1
}
return second
}
解法五:斐波那契数列
思想:
时间复杂度:O(logn)
func climbStairs(_ n: Int) -> Int {
let sqrt5 : Double = sqrt(5)
let fibn : Double = pow((1+sqrt5)/2, Double(n+1))-pow((1-sqrt5)/2, Double(n+1))
return Int(fibn/sqrt5)
}