爬楼梯 go实现 | Go 主体月

247 阅读2分钟

递归 recursion

练习:

  1. DFS 深度优先搜索
  2. 前中后序二叉树遍历
  3. 爬楼梯问题

什么样的问题可以用递归解决呢?

  1. 一个问题的解可以分解为几个子问题的解
  2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
  3. 存在递归终止条件

如何编写递归代码?

  1. 写出递推公式
  2. 找到终止条件
  3. 翻译成代码
    编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

递归注意事项

  1. 递归代码要警惕堆栈溢出。代码中存在递归,一定要设置一个最大递归深度depth,防止栈溢出
  2. 递归代码要警惕重复计算。通过一个数据结构(比如散列表)来保存已经求解过的 f(k)
  3. 函数调用耗时多、空间复杂度高

优点:代码的表达力很强,写起来简洁。

思考:规模比较大、递归层次很深地递归代码,几乎无法使用这种调试方式。对于递归代码,你有什么好的调试方法呢?

调试递归:

  1. 打印日志发现,递归值。
  2. 结合条件断点进行调试。

爬楼梯 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))
		}
	}
}