算法---递归

递归结题三部曲

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

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

相信很多初学者和我一样,这是一个思维误区,一定要走出来。既然递归是一个反复调用自身的过程,这就说明它每一级的功能都是一样的,因此我们只需要关注一级递归的解决过程即可。
在这里插入图片描述
从图中可以看出,递归是先递后归,自下往上处理。

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

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

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

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

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

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

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

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

直接套递归结题三部曲:

  1. 找终止条件:什么情况下不在递归下去?当然是树为空的时候,此时树的深度为0,递归就结束了。
  2. 找返回值:本级应该返回给上一级什么信息?题目是要我们求最大深度,我们需要返回的当然是当前级对应的树的最大深度,比如下图的右边,返回值应该是root的最大深度,root的下一级有l和r
  3. 本级应该做什么:首先,还是强调要走出之前的思维误区,递归后我们眼里的树一定是下图右边。此时就三个节点:root、root.left、root.right,根据第二步,root.left和root.right分别记录的是root的左右子树的最大深度,那么本级递归应该做的就很明确了,那就是在root的左右子树中选择一个较大的一个,加上1就是root的最大深度了,然后再返回这个深度即可
    在这里插入图片描述
int maxDepth(struct TreeNode* root){if(root==NULL){return 0;}/* root的左右子树最大高度 */int leftDepth = maxDepth(root->left);int rightDepth = maxDepth(root->right);/* 返回值是左右子树最大深度加1 */return leftDepth>rightDepth?(leftDepth+1):(rightDepth+1);
}

例2 :两两交换链表中的结点。

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

三部曲模板:

  1. 找终止条件:什么情况下终止?没有办法交换的时候,递归就停止。因此当链表只剩一个结点或者没有几点的时候,自然递归就终止了。
  2. 找返回值:本级希望向上一级返回的信息是什么?由于我们的目的是两两交换链表中相邻的结点,因此自然希望交换给上一级递归的是本级已经处理好的链表。
  3. 本级应该做什么?题目让交换相邻两个节点,所以我们应该交换相邻两个节点,涉及三个结点,两个指针,head结点,head->next结点,以及head->next->next后面已经处理好的链表,我们看做一个结点。本级递归的任务就是交换这3个结点中的前两个结点。

在这里插入图片描述

struct ListNode* swapPairs(struct ListNode* head) {/* 终止条件 */if (head == NULL || head->next == NULL) {return head;}/* 获取head->next结点 */struct ListNode*next = head->next;/* swapPairs(next->next)代表已经处理好的链表,交换两个节点,我们要让head指向这个结点 */head->next = swapPairs(next->next);/* next指向head结点 此时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这棵树是不是平衡二叉树了。
    因此,我们定义一个结构体,该结构体包含结点的深度,以及该节点是不是平衡二叉树。
struct inf{int length;//当前节点的长度bool isB;  //当前节点是不是平衡树};

返回值就是当级节点的深度以及该节点是不是平衡二叉树。如果不是平衡二叉树,返回值的深度为0

  1. 本级递归该做什么?知道了第二步的返回值后,我们应该先获得当前节点左右子树的struct inf信息,即左右子树的深度以及是不是平衡二叉树。然后判断左右子树是不是平衡二叉树以及高度差是否大于1。
struct inf{int length;//当前节点的长度bool isB;  //当前节点是不是平衡树};
struct inf *tru ;   /* 表示一个节点的初始状态是平衡二叉树 */
struct inf *fal ;   /* 表示不是平衡二叉树 */struct inf *getInf(struct TreeNode *root)
{if(root==NULL){return tru; }/* 获取左右子树的深度以及是不是平衡二叉树信息 */struct inf *right = getInf(root->right);struct inf *left  = getInf(root->left);/*判断左右子树是不是平衡二叉树*/if(right->isB == false || left->isB == false ){return fal;}/* 高度差是否大于1 */if(abs(right->length-left->length)>1){return fal;}/* 返回值左右子树深度最大值加1并且是平衡二叉树 */struct inf *ret = (struct inf *)malloc(sizeof(struct inf));ret->length = fmax(left->length,right->length)+1;ret->isB = true;return ret;}bool isBalanced(struct TreeNode* root){tru = (struct inf *)malloc(sizeof(struct inf));tru->length=0;tru->isB=true;fal = (struct inf *)malloc(sizeof(struct inf));fal->length=0;fal->isB = false;struct inf *ret = getInf(root);if(ret->isB==true)return true;elsereturn false;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379597.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

给定条件找最小值c语言程序_根据给定条件最小化n的最小步骤

给定条件找最小值c语言程序Problem statement: 问题陈述: Given a number n, count minimum steps to minimize it to 1 performing the following operations: 给定数字n ,执行以下操作,计算最少的步骤以将其最小化为1: Operat…

那个年代的苏联歌曲

小时候,不时听父亲提起电影《这里的黎明静悄悄》,怎么也想不到如此美丽的名字为什么要和战争联系起来。后来在大学看了这部电影之后,开始认为这名字是合适的,因为电影讲的是女性——战场中的女性,各自都怀揣着爱情去保…

linux系统编程---进程总结

进程控制总结1 进程创建的三种方式forkvfrokclone2 进程终止进程正常退出returnexit_exit进程异常退出进程收到某个信号,而该信号使进程终止abort3 进程等待进程等待的方法waitwaitpid4 进程替换替换原理替换函数制作一个简单的shell1 进程创建的三种方式 参考文章…

银行账务转账系统(事务处理)

流程如下: 创建项目工程如下: transfer包下的代码如下: package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils;pu…

【msdn wpf forum翻译】TextBox中文本 中对齐 的方法

原文链接:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/49864e35-1dbf-4292-a361-93f1a8400558问题:TextBox中文本中对齐,使用 TextBox.HorizontalContentAlignment"Center"行不通(TextBox.VerticalConte…

c语言 函数的参数传递示例_C语言中带有示例的remove()函数

c语言 函数的参数传递示例C语言中的remove()函数 (remove() function in C) The remove() function is defined in the <stdio.h> header file. remove()函数在<stdio.h>头文件中定义。 Prototype: 原型&#xff1a; int remove(const char* filename);Parameter…

使用ThreadLocal绑定连接资源(事务)

dao层代码如下&#xff1a; package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils; import beyond.utils.MyDataSourceUtils;public class TransferDa…

算法---栈和队列

栈和队列1 栈栈的顺序存储栈的链式存储2 队列队列的顺序存储队列的链式存储3 栈和队列的应用用栈实现队列用队列实现栈最小栈1 栈 参考文章&#xff1a; https://zhuanlan.zhihu.com/p/346164833 https://zhuanlan.zhihu.com/p/120965372#:~:text%E6%A0%88%E6%98%AF%E4%B8%80%…

在WebBrowser中通过模拟键盘鼠标操控网页中的文件上传控件

引言 这两天沉迷了Google SketchUp&#xff0c;刚刚玩够&#xff0c;一时兴起&#xff0c;研究了一下WebBrowser。 我在《WebBrowser控件使用技巧分享》一文中曾谈到过“我现在可以通过WebBrowser实现对各种Html元素的操控&#xff0c;唯独无法控制Html的上传控件”&#xff0c…

编写最简单的字符设备驱动

编写最简单的字符设备驱动1 编写驱动代码2 编写makefile3 编译和加载驱动4 编写应用程序测试驱动参考文章&#xff1a; linux驱动开发第1讲&#xff1a;带你编写一个最简单的字符设备驱动 linux驱动开发第2讲&#xff1a;应用层的write如何调用到驱动中的write 1 编写驱动代码…

Linux设备驱动开发---字符设备驱动程序

字符设备驱动程序1 主设备和次设备的概念设备号的注册和释放静态方法动态方法区别2 设备文件操作struct file_operations与struct file、struct inode关系3 分配和注册字符设备class_createcdev_adddevice_create4 字符设备驱动程序字符设备通过字符&#xff08;一个接一个的字…

Java中的异常栈轨迹和异常链

Java中允许对异常进行再次抛出&#xff0c;以提交给上一层进行处理&#xff0c;最为明显的例子为Java的常规异常。 常规异常&#xff1a;有Java所定义的异常&#xff0c;不需要异常声明&#xff0c;在未被try-catch的情况下&#xff0c;会被默认上报到main()方法。 Example: pu…

同步---信号量

信号量1 信号量2 驱动程序和测试程序3 内核的具体实现总结1 信号量 Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被占用的信号量时&#xff0c;信号量会将其放到一个等待队列&#xff0c;然后让其睡眠&#xff0c;这时处理器去执行其他代码。当持有信号量的进…

算法---KMP算法

字符串1 KMP算法状态机概述构建状态转移1 KMP算法 原文链接&#xff1a;https://zhuanlan.zhihu.com/p/83334559 先约定&#xff0c;本文用pat表示模式串&#xff0c;长度为M&#xff0c;txt表示文本串&#xff0c;长度为N&#xff0c;KMP算法是在txt中查找子串pat&#xff0…

文件上传 带进度条(多种风格)

文件上传 带进度条 多种风格 非常漂亮&#xff01; 友好的提示 以及上传验证&#xff01; 部分代码&#xff1a; <form id"form1" runat"server"><asp:ScriptManager ID"scriptManager" runat"server" EnablePageMethods&quo…

同步---自旋锁

1 自旋锁的基本概念 自旋锁最多只能被一个可执行线程持有&#xff0c;如果一个执行线程试图获得一个已经被使用的自旋锁&#xff0c;那么该线程就会一直进行自旋&#xff0c;等待锁重新可用。在任何时刻&#xff0c;自旋锁都可以防止多余一个的执行线程同时进入临界区。 Linu…

实习日志----4.播放时段参数设置

由于客户在下发广告时&#xff0c;一则广告可在多个时段播放&#xff0c;这就需要设置多个播放时段的参数。 但在这种情况下&#xff0c;我并不知道用户每次需要下发几个时段&#xff0c;所以前台不能设定死。 因此我要实现这么一个功能&#xff0c;让用户根据自己的需要来动态…

linux系统编程---线程总结

线程总结1 线程的实现线程创建线程退出线程等待线程清理2 线程的属性线程的分离线程的栈地址线程栈大小线程的调度策略线程优先级3 线程的同步互斥锁读写锁条件变量信号量线程是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该进程中的全部系统资源&#xff0c;例…

如何给Linux操作系统(CentOS 7为例)云服务器配置环境等一系列东西

1.首先&#xff0c;你得去购买一个云服务器&#xff08;这里以阿里云学生服务器为例&#xff0c;学生必须实名认证&#xff09; 打开阿里云&#xff0c;搜索学生服务器点击进入即可 公网ip为连接云服务器的主机 自定义密码为连接云服务器是需要输入的密码 购买即可 点击云服…

Linux系统编程---I/O多路复用

文章目录1 什么是IO多路复用2 解决什么问题说在前面I/O模型阻塞I/O非阻塞I/OIO多路复用信号驱动IO异步IO3 目前有哪些IO多路复用的方案解决方案总览常见软件的IO多路复用方案4 具体怎么用selectpollepolllevel-triggered and edge-triggered状态变化通知(edge-triggered)模式下…