何为递归?程序反复调用自身即是递归。
对于递归和迭代,真的是傻傻分不清。。。
幸好看到了这篇很好的文章, 三道题套路解决递归问题博主思路很清晰,而且举例非常贴切,系统的学习了一下递归问题
递归解题三部曲
何为递归?程序反复调用自身即是递归。
刚开始解决递归问题的时候,总是会去纠结这一层函数做了什么,它调用自身后的下一层函数又做了什么…然后就会觉得实现一个递归解法十分复杂,根本就无从下手。很多初学者都会这么想,但是这是一个思维误区,一定要走出来。
既然递归是一个反复调用自身的过程,这就说明它每一级的功能都是一样的,因此我们只需要关注一级递归的解决过程即可。
如上图所示,我们需要关心的主要是以下三点:
整个递归的终止条件。
一级递归需要做什么?
应该返回给上一级的返回值是什么?
因此,也就有了我们解递归题的三部曲:
第一步,找整个递归的终止条件:递归应该在什么时候结束?
第二步,找返回值:应该给上一级返回什么信息?
第三步,本级递归应该做什么:在这一级递归中,应该完成什么任务?
一定要理解这3步,这就是以后递归秒杀算法题的依据和思路。
光有理论似乎非常的抽象,接下来以题目作为例子,看看怎么套这个模版,理论和题目实践相结合,你就能慢慢理解这个模版。
例题1 求二叉树的最大深度
先看一道简单的Leetcode题目:Leetcode 104. 二叉树的最大深度
题目很简单,求二叉树的最大深度,那么直接套递归解题三部曲模版:
- 找终止条件。 什么情况下递归结束?当然是树为空的时候,此时树的深度为0,递归就结束了。
- 找返回值。
应该返回什么?题目求的是树的最大深度,我们需要从每一级得到的信息自然是当前这一级对应的树的最大深度,因此我们的返回值应该是当前树的最大深度,这一步可以结合第三步来看。 - 本级递归应该做什么。
首先,还是强调要走出之前的思维误区,递归后我们眼里的树一定是这个样子的,看下图。此时就三个节点:root、root.left、root.right,其中根据第二步,root.left和root.right分别记录的是root的左右子树的最大深度。那么本级递归应该做什么就很明确了,自然就是在root的左右子树中选择较大的一个,再加上1就是以root为根的子树的最大深度了,然后再返回这个深度即可。
具体代码实现:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
//第一步,找循环条件,树为空的时候终止循环,并返回当前深度为0
if(root == null){
return 0;
}
//第三步,本层递归应该做什么,˙这是左右子树的最大深度
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
//第二步,找返回值,返回的应该是左右子树最大的那个加一就是最大深度
return Math.max(leftDepth, rightDepth) +1;
}
}
例题2:两两交换链表中的节点
看了一道递归套路解决二叉树的问题后,有点套路搞定递归的感觉了吗?我们再来看一道Leetcode中等难度的链表的问题,掌握套路后这种中等难度的问题真的就是秒: Leetcode 24. 两两交换链表中的节点
直接上三部曲模版:
- 找终止条件。 什么情况下递归终止?没得交换的时候,递归就终止了呗。因此当链表只剩一个节点或者没有节点的时候,自然递归就终止了。
- 找返回值。
我们希望向上一级递归返回什么信息?由于我们的目的是两两交换链表中相邻的节点,因此自然希望交换给上一级递归的是已经完成交换处理,即已经处理好的链表。 - 本级递归应该做什么。
结合第二步,看下图!由于只考虑本级递归,所以这个链表在我们眼里其实也就三个节点:head、head.next、已处理完的链表部分。而本级递归的任务也就是交换这3个节点中的前两个节点,就很easy了。
具体代码实现:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
//第一步,寻找终止条件,当链表为空或者只有一个节点的时候,就没得交换了,交换终止,返回处理好的部分
if(head == null || head.next == null){
return head;
}
//第二步,本层循环应该做什么
ListNode firstNode = head;
ListNode secondNode = head.next;
//交换节点
firstNode.next = swapPairs(secondNode.next);
secondNode.next = firstNode;
//第三步,返回值,交换后应该返回已经处理好的部分
return secondNode;
}
}
例题3 对称二叉树
再来看一道关于树的题目,有点不太常规的样子,但是只要按照三部曲去套,也是很容易解决的 101. 对称二叉树
-
第一步,找终止条件
都为空指针则返回 true
只有一个为空则返回 false -
第二步,判断本层递归做什么,递归过程就是去比较是不是对称
判断两个指针当前节点值是否相等
判断 A 的右子树与 B 的左子树是否对称
判断 A 的左子树与 B 的右子树是否对称 -
第三步,返回值
返回值在这里, 指的是到底是不是对称,true 或者 false 的布尔值
写代码的时候,二三步合并了
这里牵扯到短路现象:
在递归判断过程中存在短路现象,也就是做 与 操作时,如果前面的值返回 false 则后面的不再进行计算
具体实现代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return isMirror(root, root);
}
public boolean isMirror(TreeNode t1, TreeNode t2){
//第一步,递归结束条件:都为空指针则返回 true,只有一个为空则返回 false
if(t1 == null && t2 == null) return true;
if(t1 == null || t2 == null) return false;
//第二步,递归过程,判断到底是不是对称,分情况分析
//第三步,返回值,返回是否对称的布尔值
return (t1.val == t2.val) && isMirror(t1.right,t2.left) && isMirror(t1.left,t2.right);
}
}
大概就是上面这样,脑子里有点理论性的知识,可以更快的想到应该怎么下手,代码具体写成什么样还是要看个人的能力,希望大家能理解这个很好用的套路,套路在手,递归我有。
在刷题过程中遇到的能用这个套路解决的题,真的太多了,大部分链表和树的递归题都能这么做,因为树和链表天生就是适合递归的结构。
确认过眼神,这是递归的套路,套路获递归心~~~