递归 recursion
练习:
- DFS 深度优先搜索
- 前中后序二叉树遍历
- 爬楼梯问题
什么样的问题可以用递归解决呢?
- 一个问题的解可以分解为几个子问题的解
- 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
- 存在递归终止条件
如何编写递归代码?
- 写出递推公式
- 找到终止条件
- 翻译成代码
编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。
递归注意事项
- 递归代码要警惕堆栈溢出。代码中存在递归,一定要设置一个最大递归深度depth,防止栈溢出
- 递归代码要警惕重复计算。通过一个数据结构(比如散列表)来保存已经求解过的 f(k)
- 函数调用耗时多、空间复杂度高
优点:代码的表达力很强,写起来简洁。
思考:规模比较大、递归层次很深地递归代码,几乎无法使用这种调试方式。对于递归代码,你有什么好的调试方法呢?
调试递归:
- 打印日志发现,递归值。
- 结合条件断点进行调试。
爬楼梯 go 递归实现
package main
import (
"fmt"
_ "net/http/pprof"
"testing"
)
// 避免重复
var stairMap = make(map[int]int)
// 避免堆栈溢出,设置最大递归深度
var depth uint
// 递归
func climbStairs(n int) int {
depth++
if depth > 100 {
panic("depth > 100")
}
if n == 1 {
return 1
}
if n == 2 {
return 2
}
if climb, ok := stairMap[n]; ok {
return climb
} else {
var nums int
nums += climbStairs(n-1) + climbStairs(n-2)
stairMap[n] = nums
return nums
}
}
func TestClimbStairs(t *testing.T) {
tests := []struct {
n int
expected int
}{
{2, 2},
{3, 3},
{4, 5},
{5, 8},
{45, 1836311903},
}
for _, tt := range tests {
actual := climbStairs(tt.n)
if tt.expected != actual {
t.Error(fmt.Sprintf("TestRotate failed. expect: %v , actual: %v", tt.expected, actual))
}
}
}