递归解题三部曲

何为递归?程序反复调用自身即是递归。

我自己在刚开始解决递归问题的时候,总是会去纠结这一层函数做了什么,它调用自身后的下一层函数又做了什么…然后就会觉得实现一个递归解法十分复杂,根本就无从下手。

相信很多初学者和我一样,这是一个思维误区,一定要走出来。既然递归是一个反复调用自身的过程,这就说明它每一级的功能都是一样的,因此我们只需要关注一级递归的解决过程即可。

如上图所示,我们需要关心的主要是以下三点:

  1. 整个递归的终止条件。
  2. 一级递归需要做什么?
  3. 应该返回给上一级的返回值是什么?

因此,也就有了我们解递归题的三部曲:

  1. 找整个递归的终止条件:递归应该在什么时候结束?
  2. 找返回值:应该给上一级返回什么信息?
  3. 本级递归应该做什么:在这一级递归中,应该完成什么任务?

一定要理解这3步,这就是以后递归秒杀算法题的依据和思路。

递归的三大要素

第一要素:明确你这个函数想要干什么

对于递归,我觉得很重要的一个事就是,这个函数的功能是什么,他要完成什么样的一件事,而这个,是完全由你自己来定义的。也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。

第二要素:寻找递归结束条件

所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

第三要素:找出函数的等价关系式

第三要素就是,我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。

例1:求二叉树的最大深度

先看一道简单的Leetcode题目: Leetcode 104. 二叉树的最大深度

题目很简单,求二叉树的最大深度,那么直接套递归解题三部曲模版:

  1. 找终止条件。 什么情况下递归结束?当然是树为空的时候,此时树的深度为0,递归就结束了。
  2. 找返回值。 应该返回什么?题目求的是树的最大深度,我们需要从每一级得到的信息自然是当前这一级对应的树的最大深度,因此我们的返回值应该是当前树的最大深度,这一步可以结合第三步来看。
  3. 本级递归应该做什么。 首先,还是强调要走出之前的思维误区,递归后我们眼里的树一定是这个样子的,看下图。此时就三个节点:root、root.left、root.right,其中根据第二步,root.left和root.right分别记录的是root的左右子树的最大深度。那么本级递归应该做什么就很明确了,自然就是在root的左右子树中选择较大的一个,再加上1就是以root为根的子树的最大深度了,然后再返回这个深度即可。

具体代码如下:

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):def maxDepth(self, root):""":type root: TreeNode:rtype: int"""if root is None:return 0left_height = self.maxDepth(root.left)right_height = self.maxDepth(root.right)return max(left_height, right_height) + 1

例2:两两交换链表中的节点

看了一道递归套路解决二叉树的问题后,有点套路搞定递归的感觉了吗?我们再来看一道Leetcode中等难度的链表的问题,掌握套路后这种中等难度的问题真的就是秒:Leetcode 24. 两两交换链表中的节点

直接上三部曲模版:

  1. 找终止条件。 什么情况下递归终止?没得交换的时候,递归就终止了呗。因此当链表只剩一个节点或者没有节点的时候,自然递归就终止了。
  2. 找返回值。 我们希望向上一级递归返回什么信息?由于我们的目的是两两交换链表中相邻的节点,因此自然希望交换给上一级递归的是已经完成交换处理,即已经处理好的链表。
  3. 本级递归应该做什么。 结合第二步,看下图!由于只考虑本级递归,所以这个链表在我们眼里其实也就三个节点:head、head.next、已处理完的链表部分。而本级递归的任务也就是交换这3个节点中的前两个节点,就很easy了。

附上代码:

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):def swapPairs(self, head):""":type head: ListNode:rtype: ListNode"""#终止条件:链表只剩一个节点或者没节点了,没得交换了。返回的是已经处理好的链表if head is None or head.next is None:return head#一共三个节点:head, next, swapPairs(next.next)#下面的任务便是交换这3个节点中的前两个节点newHead = head.nexthead.next = self.swapPairs(newHead.next) newHead.next = head#根据第二步:返回给上一级的是当前已经完成交换后,即处理好了的链表部分return newHead

例3:反转单链表

206. 反转链表

反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1

1. 定义递归函数功能

假设函数 reverseList(head) 的功能是反转但链表,其中 head 表示链表的头节点

2.寻找结束条件

当链表只有一个节点,或者如果是空表的话,你应该知道结果吧?直接啥也不用干,直接把 head 返回

3. 寻找等价关系

它的等价条件中,一定是范围不断在缩小,对于链表来说,就是链表的节点个数不断在变小,所以,如果你实在找不出,你就先对 reverseList(head.next) 递归走一遍,看看结果是咋样的。

先缩小范围,先对 2->3->4递归下试试,即代码如下

class Solution:def reverseList(self, head: ListNode) -> ListNode:if not head or not head.next:return headnewHead = self.reverseList(head.next)

们在第一步的时候,就已经定义了 reverseLis t函数的功能可以把一个单链表反转,所以,我们对 2->3->4反转之后的结果应该是这样:

我们把 2->3->4 递归成 4->3->2。不过,1 这个节点我们并没有去碰它,所以 1 的 next 节点仍然是连接这 2。

接下来呢?该怎么办?

其实,接下来就简单了,我们接下来只需要把节点 2 的 next 指向 1,然后把 1 的 next 指向 null,不就行了?,即通过改变 newList 链表之后的结果如下:

也就是说,reverseList(head) 等价于 reverseList(head.next) + 改变一下1,2两个节点的指向。好了,等价关系找出来了,代码如下(有详细的解释):

class Solution:def reverseList(self, head: ListNode) -> ListNode:if not head or not head.next:return headnewHead = self.reverseList(head.next)#改变 1,2节点的指向。#通过 head.next获取节点2t1 = head.next#让 2 的 next 指向 2t1.next = head#1 的 next 指向 null.head.next = None#把调整之后的链表返回。return newHead

例4:平衡二叉树

那么请你先不看以下部分,尝试解决一下这道easy难度的Leetcode题(个人觉得此题比上面的medium难度要难):Leetcode 110. 平衡二叉树

我觉得这个题真的是集合了模版的精髓所在,下面套三部曲模版:

  1. 找终止条件。 什么情况下递归应该终止?自然是子树为空的时候,空树自然是平衡二叉树了。

  2. 应该返回什么信息:

    为什么我说这个题是集合了模版精髓?正是因为此题的返回值。要知道我们搞这么多花里胡哨的,都是为了能写出正确的递归函数,因此在解这个题的时候,我们就需要思考,我们到底希望返回什么值?

    何为平衡二叉树?平衡二叉树即左右两棵子树高度差不大于1的二叉树。而对于一颗树,它是一个平衡二叉树需要满足三个条件:它的左子树是平衡二叉树,它的右子树是平衡二叉树,它的左右子树的高度差不大于1。换句话说:如果它的左子树或右子树不是平衡二叉树,或者它的左右子树高度差大于1,那么它就不是平衡二叉树。

    而在我们眼里,这颗二叉树就3个节点:root、left、right。那么我们应该返回什么呢?如果返回一个当前树是否是平衡二叉树的boolean类型的值,那么我只知道left和right这两棵树是否是平衡二叉树,无法得出left和right的高度差是否不大于1,自然也就无法得出root这棵树是否是平衡二叉树了。而如果我返回的是一个平衡二叉树的高度的int类型的值,那么我就只知道两棵树的高度,但无法知道这两棵树是不是平衡二叉树,自然也就没法判断root这棵树是不是平衡二叉树了。

  3. 本级递归应该做什么。 知道了第二步的返回值后,这一步就很简单了。目前树有三个节点:root,left,right。我们首先判断left子树和right子树是否是平衡二叉树,如果不是则直接返回false。再判断两树高度差是否不大于1,如果大于1也直接返回false。否则说明以root为节点的子树是平衡二叉树,那么就返回true和它的高度。

判断左右子树的高度差是否超过1,依次递归

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):def isBalanced(self, root):""":type root: TreeNode:rtype: bool"""if root is None:return Trueif abs(self.getHeight(root.left) - self.getHeight(root.right)) > 1:return Falsereturn self.isBalanced(root.left) and self.isBalanced(root.right)def getHeight(self, node):if node is None:return 0return max(self.getHeight(node.left), self.getHeight(node.right)) + 1

一些可以用这个套路解决的题

暂时就写这么多啦,作为一个高考语文及格分,大学又学了工科的人,表述能力实在差因此啰啰嗦嗦写了一大堆,希望大家能理解这个很好用的套路。

下面我再列举几道我在刷题过程中遇到的也是用这个套路秒的题,真的太多了,大部分链表和树的递归题都能这么秒,因为树和链表天生就是适合递归的结构。

我会随时补充,正好大家可以看了上面三个题后可以拿这些题来练练手,看看自己是否能独立快速准确的写出递归解法了。

Leetcode 101. 对称二叉树

Leetcode 111. 二叉树的最小深度

Leetcode 226. 翻转二叉树

Leetcode 617. 合并二叉树

Leetcode 654. 最大二叉树

Leetcode 83. 删除排序链表中的重复元素

https://mp.weixin.qq.com/s/PgSSYc50ajnbh8zD6Ei07g

https://mp.weixin.qq.com/s/mJ_jZZoak7uhItNgnfmZvQ

https://lyl0724.github.io/2020/01/25/1/

[递归]递归问题解题思路相关推荐

  1. LeetCode—笔记—51、N皇后——递归回溯,个人思路,简单易懂

    LeetCode-笔记-51.N皇后--递归回溯,个人思路,简单易懂 51. N 皇后 n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 给你一个整数 ...

  2. 牛顿插值多项式 Python 循环和递归两种实现思路

    这里不介绍牛顿插值多项式数学推导,只提供Python循环和递归实现的思路与代码,想要学习牛顿插值多项式的同学可以去看数值分析课本等. 这里提供循环和递归两种思路,因为我是先想的递归,完整的代码和思路都 ...

  3. 剑指offer第二版答案详细版(带详细解题思路)

    1.滑动窗口的最大值(剑指offer原59题) 解题思路:其实是一个队列的问题,用一个队列去维护当前窗口中的所有元素:首先将超出窗口中的队头元素先删掉,然后将新的元素插入当前窗口中,插入时要判断新插入 ...

  4. Leetcode 202. 快乐数 解题思路及C++实现

    解题思路: 用递归的方法,出现1,就返回true,这里用了 unordered_map 来记录是否会出现循环.(也可以用unordered_set) 通过计算余数和商,来得到每个位置数字的平方和.具体 ...

  5. Leetcode 90. 子集 II 解题思路及C++实现

    解题思路: 经典的深度优先搜索问题. 这里,根据子集的元素个数 i ,分别进行 dfs .因为 nums 数组中有重复元素,所以在 dfs 程序中,需要有一个去重的判断. 判断逻辑为:当前元素与上一个 ...

  6. Leetcode 375. 猜数字大小 II 解题思路及C++实现

    方法一:递归 解题思路: 设置一个dp数组,dp[i][j] 表示从数字 i 到 j ,保证猜中所选数字所需的最小花费. 在数字 i 到 j 之间进行猜测时,我们选择数字 i < k < ...

  7. Leetcode 207. 课程表 解题思路及C++实现

    解题思路: 这是一个判断是否是有向无环图的题,也是拓扑排序题,网上的解决方案大多是从图的角度来解释的,比较复杂,下面先不管图论的内容,直接就深度优先搜索的方法来思考. 首先是建立一个graph,有多少 ...

  8. Leetcode 200. 岛屿数量 解题思路及C++实现

    解题思路: 典型的深度优先搜索问题,跟第130题 被围绕的区域 有点像,只不过这里不仅要找出被水包围的岛屿,还要计算这些岛屿的总数. 使用深度优先搜索的方法,大循环是遍历整个grid数组(两个for循 ...

  9. Leetcode 133. 克隆图 解题思路及C++实现

    解题思路: 这道题目,一开始看了几遍都没看懂题意,后来找网上的答案才明白是要干啥.其实就是做一次深拷贝的实现.也是比较典型的深度优先搜索问题. 使用C++中的unordered_map<Node ...

  10. Leetcode 130. 被围绕的区域 解题思路及C++实现

    解题思路: 这是一个典型的深度优先搜索问题,在程序处理过程中,将未被'X'包围的'O'标记为符号'*'. 先遍历数组边界上的字符'O',将其标记为'*',然后对出现'*'的位置,通过递归dfs,遍历其 ...

最新文章

  1. acitivity 和fragment 通信,使用广播来传递信息的问题
  2. 具有absolute、relative、fixed的div设置宽度和高度的效果
  3. 【云图】如何制作附近实体店的地图?-微信微博支付宝
  4. 编辑流程图_作为一名采购商,做不好采购?送你5套采购流程图模板
  5. 海底声纳Sonar探测编辑软件开发纪事
  6. JSP中include指令的乱码问题
  7. android多语言编码格式,在Android中使用国家/地区代码以编程方式更改语言
  8. linux tomcat 开启apr,tomcat开启APR
  9. C++学习之路 | PTA乙级—— 1027 打印沙漏 (20 分)(精简)
  10. 数据类型和Json格式
  11. python语法_算数运算+赋值运算符+比较运算符+逻辑运算符
  12. java 常量 类型_Java的常量及数据类型。
  13. IntelliJ Idea一些常用快捷键
  14. ThreadInfo结构和内核栈的两种关系
  15. Android如何 如何关闭 DM-verity
  16. 基于双目摄像头SGBM视差图的障碍物提取
  17. mybatis中只查询部分字段的处理方式
  18. 自然语言处理从零到入门 BERT
  19. QPainter 画扇形
  20. iOS wkWebview播放HTML5 video视频 自动全屏问题解决

热门文章

  1. Oracle 11gR2 RAC Service-Side TAF 配置示例
  2. PostgreSQL 10.1 手册_部分 I. 教程_第 2 章 SQL语言
  3. C++11 作用域内枚举
  4. 英国电信公司沃达丰遭到网络攻击
  5. Packer创建阿里云本地镜像
  6. json格式数据,将数据库中查询的结果转换为json, 然后调用接口的方式返回json(方式一)...
  7. 阿里技术高P访谈之张俭恭:情义是阿里与外企的最大不同
  8. 安卓拒绝服务漏洞分析及漏洞检测
  9. java远程调用linux的命令或者脚本
  10. Ext JS 5初探(二) ——Bootstrap.js