递归是一种应用非常广泛的算法
递归需要满足的三个条件
1. 一个问题的解可以分解为几个子问题的解
2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
3. 存在递归终止条件
写递归代码最关键的是写出递推公式,找到终止条件
示例:
有 n 个台阶,每次你可以跨 1 个台阶或者 2 个台阶,请问走这 n 个台阶有多少种走法?
分析:
n=1时,1种;
n=2时,2种;
n>2时,第一步走1个台阶,则有f(n-1)种;第一步走2个台阶,则有f(n-2)种;
所以 f(n) = f(n-1) + f(n-2)
func f(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 2
}
return f(n-1) + f(n-2)
}
写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。
注意点:
1.要警惕堆栈溢出,限制递归调用的最大深度.
2.要警惕重复计算。通过一个数据结构(比如散列表)来保存已经求解过的 f(k)。当递归调用到 f(k) 时,先看下是否已经求解过了。
例题:
https://leetcode-cn.com/problems/climbing-stairs/
func climbStairs(n int) int {
//方法2
// var tempMap = make(map[int]int)
// return climb2(0, n, tempMap)
//方法1,3,4
return climb4(n)
}
//dp原理:dp[i] = dp[i-1] + dp[i-2]
//方法1 暴力法
func climb1(i, n int) int {
if i == n {
return 1
}
if i > n {
return 0
}
return climb1(i+1, n) + climb1(i+2, n)
}
//方法2 记忆
func climb2(i, n int, tempMap map[int]int) int {
if i == n {
return 1
}
if i > n {
return 0
}
if tempMap[i] == 0 {
tempMap[i] = climb2(i + 1, n, tempMap) + climb2(i + 2, n, tempMap)
}
return tempMap[i]
}
//方法3 动态规划
func climb3(n int) int {
var tempMap = make(map[int]int)
tempMap[1] = 1
tempMap[2] = 2
for i := 3; i <= n; i++ {
tempMap[i] = tempMap[i-1] + tempMap[i-2]
}
return tempMap[n]
}
//方法4 动态规划 + 优化
func climb4(n int) int {
if n == 1 {
return 1
}
one, two := 1, 2
for i := 3; i <= n; i++ {
one, two = two, one + two
}
return two
}
因为递归调用一次就会在内存栈中保存一次现场数据,所以在分析递归代码空间复杂度时,需要额外考虑这部分的开销.