递归的简单理解与使用

递归的简单使用

例1:104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7
 // 这是一个二叉树
 Definition for a binary tree node.
 type TreeNode struct {
     Val int
     Left *TreeNode
     Right *TreeNode
 }

解题思路

本题的目的是找出二叉树的最大深度,
而二叉树分为左子树与右子数

  • 首先我们知道若结点为空,则二叉树深度为0
  • 其次分别找出左子树的最大深度和右子树的最大深度
  • 那么到这里我发现,本题的目的是找出二叉树的最大深度,而这个问题又可以被分解为找出
    • 1、左子树的最大深度
    • 2、右子树的最大深度
  • 二叉树的最大深度就是取***左子树和右子数中最大的那一个***
  • 这里我想到了递归
  • 了解过递归的小伙伴应该都清楚,递归就是通过不断的调用自身来解决问题
  • 。。。。递归为什么可以通过调用自身来解决问题?不知道大家有没有思考过这个问题
  • 这得从函数说起,什么是函数?函数本质可以理解为解决一个特定问题的工具
  • 递归通过不断的使用相同的工具来达到想要的目的
    • 这题我之所以想到了用递归,是因为这题的大问题,以及分解出来的小问题,都是类似的—>求二叉树的深度
    • 那么我们就可以不断用同一个工具来解决二叉树深度这个问题
  • 所以本题的最终目标,以及我刚刚分解出来的子问题都可以定义一个函数来解决
//此处假设,我定义了一个可以判断所有二叉树深度的函数
//以后我只要我调用maxDepth(root),可以得到root二叉树的深度	// root是一个二叉树
func maxDepth(root *TreeNode) int {
//此处需要大家理解一下!!!这是一个可以判断所有二叉树深度的函数!!!
}

那么我们可以根据这个函数解决上面的两小问题

//左子树的最大深度
maxDepth(root.left)	// 这里我们算出了左子树的最大深度
//右子树的最大深度
maxDepth(root.right)	// 这里我们算出了右子树的最大深度

上面的思路有提到:二叉树的最大深度是取左子树和右子数中最大的那一个
那么可以将那个比较抽象的maxDepth函数完善一下啦

func maxDepth(root *TreeNode) int {
    left := maxDepth(root.Left)
    right := maxDepth(root.Right)
    if left > right {	    //判断谁最深,谁深返回谁
        return left + 1     //因为是子树,所以返回结果得+1
    }
    return right + 1        //因为是子树,所以返回结果得+1
}

最后,函数需要有一个结束的条件

如果他给我的一个root(二叉树),里面什么也没有(二叉树值为nil)

那么我们只需要在函数里面加一个判断语句就OK了,二叉树若结点为空,则二叉树深度为0,函数return 0结束

func maxDepth(root *TreeNode) int {
    if root == nil{     //判断若结点为空,则二叉树深度为0
        return 0
    }
    left := maxDepth(root.Left)
    right := maxDepth(root.Right)
    if left > right {   //判断谁最深! 谁深返回谁!!
        return left + 1 //因为是子树,所以返回结果得+1
    }
    return right + 1    //因为是子树,所以返回结果得+1
}

总结

  • 1、找出题目的想问你什么,定义一个可以解决此类问题的函数
  • 2、分解问题,把大问题分解为小问题(这一步是最难的)
  • 3、整合结果,用定义的函数解决小问题,以此得出大问题的答案
  • 4、找到函数递归结束的条件,退出递归函数

例2:62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:

img

输入:m = 3, n = 7

输出:28

示例 2:

输入:m = 3, n = 2

输出:3

解释:

从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向下

示例 3:

输入:m = 7, n = 3

输出:28

示例 4:

输入:m = 3, n = 3

输出:6

提示:

  • 1 <= m, n <= 100

  • 题目数据保证答案小于等于 2 * 109

1、根据我上面总结的,首先找出题目的想问你什么,并且定义一个可以解决此类问题的函数

func uniquePaths(m int, n int) int {	//	定义一个可以得到你走多少条路径的函数

}

2、分解问题,把大问题分解为小问题

这个问题可以分解为:

  • 1、先往右走一步
  • 2、先往下走一步

那么我们可以根据这个函数解决上面的两小问题

//先往右走一步,走多久能到终点
uniquePaths(m, n-1)	// 这里我们算出了先往右走到终点的路径数
//先往下走一步,走多久能到终点
uniquePaths(m-1, n)	// 这里我们算出了先往下走到终点的路径数

3、整合结果,用定义的函数解决小问题,以此得出大问题的答案

func uniquePaths(m int, n int) int {
   return uniquePaths(m, n-1) + uniquePaths(m-1, n)	
}

4、找到函数递归结束的条件,退出递归函数

func uniquePaths(m int, n int) int {
    if m == 1 || n == 1
        return 1
    if m == 2
        return n
    if n == 2
        return m
    return uniquePaths(m, n-1) + uniquePaths(m-1, n)
}

最后得到这个,但是
在这里插入图片描述
执行到是可以执行,但也就只能运行运行小数目了,一遇到稍微大点的数目就。。。
在这里插入图片描述

递归 · 优化

解题思路

让我们看看递归调用图:
在这里插入图片描述
我们看到上面图中红色,蓝色,还有紫色圈住的都表示重复的计算,所以有一种方式就是把计算过的值使用数组或者 HashMap 保存,我们用数组来保存,用的时候先查看是否计算过,如果计算过就直接拿来用。

例如 arr[m-1][n-1] = uniquePaths(m, n-1) + uniquePaths(m-1, n) 还没有计算过的时候,我们让 arr[n][n] 等于一个特殊值,例如 arr[n][n] = 0。

当我们要判断的时候,如果值等于0,则证明 uniquePaths(m, n-1) + uniquePaths(m-1, n) 没有计算过,否则, arr[m-1][n-1] = uniquePaths(m, n-1) + uniquePaths(m-1, n) 就已经计算过了,且 arr[m-1][n-1] = uniquePaths(m, n-1) + uniquePaths(m-1, n)。直接把值取出来就行了。代码如下:

var arr [100][100]int
func uniquePaths(m int, n int) int {
	if m == 1 || n == 1 {
		return 1
	}
	if m == 2{
		return n
	}
	if n == 2{
		return m
	}
	// 先判断有没计算过
	if arr[m-1][n-1] != 0 {
		// 计算过,直接返回
		return arr[m-1][n-1]
	} else {
		// 没有计算过,递归计算,并且把结果保存到 arr数组里
		arr[m-1][n-1] = uniquePaths(m, n-1) + uniquePaths(m-1, n)
		return arr[m-1][n-1]
	}
}

也就是说,使用递归的时候,必要须要考虑有没有重复计算,如果重复计算了,一定要把计算过的状态保存起来,通过这个步骤,可以大大简化计算量

递推

对于递归的问题,我们一般都是从上往下递归的,直到递归到最底,再一层一层着把值返回。

不过,有时候当 n 比较大的时候,例如当 n = 10000 时,那么必须要往下递归10000层直到 n <=1 才将结果慢慢返回,如果n太大的话,可能栈空间会不够用。

对于这种情况,其实我们是可以考虑自底向上的做法的。例如我知道

uniquePaths(0,n) = n;

uniquePaths(n,0) = n;

uniquePaths(1,1) = uniquePaths(0,1)+uniquePaths(0,1) = 1+1 = 2;

uniquePaths(2,1) = uniquePaths(1,1) + uniquePaths(1,0) = 2+1 =3;

uniquePaths(1,2) = uniquePaths(0,2) + uniquePaths(1,1) = 2+2 =3;

uniquePaths(2,2) = uniquePaths(1,2) + uniquePaths(2,1) = 3+3 =6;

那么我们就可以推出 uniquePaths(m,n)

因此,我们可以考虑使用自底向上的方法来取代递归,代码如下:

例如本题:

func uniquePaths(m int, n int) int {
	var arr [100][100]int
	//第一列都是1
	for i := 0; i < m; i++ {
		arr[i][0] = 1
	}
	//第一行都是1
	for i := 0; i < n; i++ {
		arr[0][i] = 1
	}

	//这里是递推公式
	for i := 1; i < m; i++{
		for j := 1; j < n; j++{
			arr[i][j] = arr[i - 1][j] + arr[i][j - 1]
		}
	}
	return arr[m - 1][n - 1];
}

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

1、点个赞呗,可以让更多的人看到这篇文章,顺便激励下我,哈哈哈。

2、关注我的原创微信公众号「👇👇👇」,专注于写基础知识,基础学习,适合新手小白(其实就是我在学什么,整理了就会更新的)得空可能还会推荐一波我喜欢的软件或者插件。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值