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

递归解题三部曲

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

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

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

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

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

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

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

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

但这么说好像很空,我们来以题目作为例子,看看怎么套这个模版,相信3道题下来,你就能慢慢理解这个模版。之后再解这种套路递归题都能直接秒了。

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

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

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

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

具体Java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {public int maxDepth(TreeNode root) {//终止条件:当树为空时结束递归,并返回当前深度0if(root == null){return 0;}//root的左、右子树的最大深度int leftDepth = maxDepth(root.left);int rightDepth = maxDepth(root.right);//返回的是左右子树的最大深度+1return Math.max(leftDepth, rightDepth) + 1;}
}

当足够熟练后,也可以和Leetcode评论区一样,很骚的几行代码搞定问题,让之后的新手看的一脸懵逼(这道题也是我第一次一行代码搞定一道Leetcode题):

1
2
3
4
5
class Solution {public int maxDepth(TreeNode root) {return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;}
}

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

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

直接上三部曲模版:

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

附上Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {public ListNode swapPairs(ListNode head) {//终止条件:链表只剩一个节点或者没节点了,没得交换了。返回的是已经处理好的链表if(head == null || head.next == null){return head;}//一共三个节点:head, next, swapPairs(next.next)//下面的任务便是交换这3个节点中的前两个节点ListNode next = head.next;head.next = swapPairs(next.next);next.next = head;//根据第二步:返回给上一级的是当前已经完成交换后,即处理好了的链表部分return next;}
}

例3:平衡二叉树

相信经过以上2道题,你已经大概理解了这个模版的解题流程了。

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

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

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

  2. 应该返回什么信息:

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

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

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

    因此,这里我们返回的信息应该是既包含子树的深度的int类型的值,又包含子树是否是平衡二叉树的boolean类型的值。可以单独定义一个ReturnNode类,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    class ReturnNode{boolean isB;int depth;//构造方法public ReturnNode(boolean isB, int depth){this.isB = isB;this.depth = depth;}
    }
    
  3. 本级递归应该做什么。 知道了第二步的返回值后,这一步就很简单了。目前树有三个节点:root,left,right。我们首先判断left子树和right子树是否是平衡二叉树,如果不是则直接返回false。再判断两树高度差是否不大于1,如果大于1也直接返回false。否则说明以root为节点的子树是平衡二叉树,那么就返回true和它的高度。

具体的Java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {//这个ReturnNode是参考我描述的递归套路的第二步:思考返回值是什么//一棵树是BST等价于它的左、右俩子树都是BST且俩子树高度差不超过1//因此我认为返回值应该包含当前树是否是BST和当前树的高度这两个信息private class ReturnNode{boolean isB;int depth;public ReturnNode(int depth, boolean isB){this.isB = isB;this.depth = depth;}}//主函数public boolean isBalanced(TreeNode root) {return isBST(root).isB;}//参考递归套路的第三部:描述单次执行过程是什么样的//这里的单次执行过程具体如下://是否终止?->没终止的话,判断是否满足不平衡的三个条件->返回值public ReturnNode isBST(TreeNode root){if(root == null){return new ReturnNode(0, true);}//不平衡的情况有3种:左树不平衡、右树不平衡、左树和右树差的绝对值大于1ReturnNode left = isBST(root.left);ReturnNode right = isBST(root.right);if(left.isB == false || right.isB == false){return new ReturnNode(0, false); }if(Math.abs(left.depth - right.depth) > 1){return new ReturnNode(0, false);}//不满足上面3种情况,说明平衡了,树的深度为左右俩子树最大深度+1return new ReturnNode(Math.max(left.depth, right.depth) + 1, true);}
}

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

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

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

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

Leetcode 101. 对称二叉树

Leetcode 111. 二叉树的最小深度

Leetcode 226. 翻转二叉树:这个题的备注是最骚的。Mac OS下载神器homebrew的大佬作者去面试谷歌,没做出来这道算法题,然后被谷歌面试官怼了:”我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了。”

Leetcode 617. 合并二叉树

Leetcode 654. 最大二叉树

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

三道题套路解决递归问题相关推荐

  1. 3道题彻底搞定:套路解决递归问题

    转自3道题彻底搞定:套路解决递归问题 前言 相信不少同学和我一样,在刚学完数据结构后开始刷算法题时,遇到递归的问题总是很头疼,而一看解答,却发现大佬们几行递归代码就优雅的解决了问题.从我自己的学习经历 ...

  2. 我也是LeetCode周赛“三道题选手”啦 第270场周赛

    第270场周赛小结 我的Weekly Contest 270战况 什么是LeetCode周赛? show my code! 复盘解决Hard题 我的Weekly Contest 270战况 参加周赛有 ...

  3. 【二叉树】三种方式解决翻转二叉树问题

    题目描述 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点. 思路: 可能大家开始看都觉得很懵,但是我们要抓住这道题的本题.所谓的翻转二叉树还不如就叫交换二叉树左右子节点,说到这里是 ...

  4. 这三道题,总有一道你是答不出来的

    全世界只有3.14 % 的人关注了 数据与算法之美 大家好,我是最近过得不太舒心的卢sir. 经常被小思妹提的各种奇葩数学题搞得云里雾里的. 我一看数学题就想做,没想到这些题目都是奇葩中的奇葩,不是那 ...

  5. 联想面试智力题,听说前面三道题必考(有部份解答)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 联想面试 ...

  6. 2021年春季学期-信号与系统-第三次作业参考答案-第三道题

    本文是 2021年春季学期-信号与系统-第三次作业参考答案 中的参考答案. ▌第三道题 3. 把x[n]x\left[ n \right]x[n]升液体AAA和100−x[n]100 - x\left ...

  7. LeetCode547. Friends Circles 利用union find | bfs | dfs三种方法解决

    问题来源 此题来源于LeetCode547. Friend Circles,主要运用了并查集(union find).广度优先遍历(bfs)和深度优先遍历(bfs)三种方法解决. 问题简述 给定一个N ...

  8. Qt工作笔记-三种方式解决Qt5中文编码问题

    目录 前言 方式一 方式二 方式三 三种方式解决Qt5中文编码问题 前言 这里不谈原理,只说如何去做! 这里本人自己总结下,今天遇到了新的编码问题,在此记录下,方面以后快速调用. 把这三种方法顺序试下 ...

  9. 尾调用优化 java_基于Java8函数式编程求一个List的全部子集|尾调用优化解决递归性能问题...

    目录 基于函数式编程求一个List的全部子集 代码来自<Java8 in Action>,思路和其他递归解决方法一致,但不同的地方在concat方法 public static List& ...

最新文章

  1. 渐进式Web应用程序的深入概述
  2. Linux-鸟菜-5-文件权限
  3. 如何在Visual Studio 2010中使用CppUTest建立TDD的Code Kata的环境
  4. 鸿蒙9月11日,鸿蒙2.0版9月11日正式发布,华为将迎来新的突破,具有历史意义!...
  5. Drupal 通过API动态的添加样式文件
  6. python中如何安装django库_python安装好django库,新建django项目
  7. Typecho添加投稿功能插件
  8. linux stubs 32.h,解决 error: gnu/stubs-32.h: No such file or directory
  9. 2023杭州电子科技大学计算机考研信息汇总
  10. excel复选框_在Excel公式中使用复选框结果
  11. 新时期文学专题研究【1】
  12. EINT、DINT、ERTM、DRTM和EALLOW、EDIS、ESTOP0解析
  13. 如何计算平台的可用性?
  14. 安装arcgis的时候应用程序无法正常启动0xc000007b解决方法
  15. python递归函数例子_Python递归函数经典案例-汉诺塔问题
  16. 双基因突变患者_一例 Kallmann 综合征患者双基因突变分析
  17. 彻底卸载Tomcat
  18. 半定制器件课程设计——基于FPGA的PS2键盘人机输入显示系统
  19. 拉萨java培训_西藏拉萨PHP培训地址在哪学费多少
  20. ffmpeg中如何设置不含SEI,如何自定义SEI

热门文章

  1. 归并排序(merge sort)算法实现
  2. idea中,springboot项目部署到docker
  3. 一个题目涉及到的50个Sql语句
  4. FastDFS分布式文件系统
  5. 第23天:js-数据类型转换
  6. android获取屏幕宽高与获取控件宽高
  7. html中的rel,rev是什么?
  8. OSX上Docker快速上手-以部署node.js环境为例
  9. php 长整型转字符串 (convert long to string)
  10. 在sphinx中处理使用特殊字符时所引起错误的办法