递归究竟是什么?如何快速编写正确的递归代码? —— 力扣经典面试题详解

递归究竟是什么?如何快速编写正确的递归代码? —— 力扣经典面试题详解

  • 一、递归
    • 1.1 什么是递归?
    • 1.2 为什么会用到递归?
    • 1.3 如何快速编写正确的递归代码?
  • 二、力扣相关笔试题解析
    • [面试题 08.06. 汉诺塔问题](https://leetcode.cn/problems/hanota-lcci/description/)
    • [21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/description/)
    • [LCR 024. 反转链表](https://leetcode.cn/problems/UHnkqh/description/)
    • [24. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/description/)

一、递归

1.1 什么是递归?

下面是来自百度百科对递归算法的定义:

 递归是一种算法设计技术,它允许一个函数在其定义或说明中有直接或间接调用自身的方法。
 递归在数学和计算机科学中有着广泛的应用,它通过将复杂问题分解为规模较小、形式相同的子问题来求解。递归的基本原理包括:每一级的函数调用都有自己的变量;每一次函数调用都会有一次返回;递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序;递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反;虽然每一级递归都有自己的变量,但是函数代码并不会得到复制。

简单来说,递归的本质就是函数自己调用自己!

1.2 为什么会用到递归?

 在回答这个问题前,我们先来看看二叉树的先序遍历是如何实现的?

 所谓先序遍历即依次遍历二叉树的根节点、左子树、右子树。在遍历过程中,我们发现左子树和右子树的遍历同样可以采用先序遍历来实现。即将先序遍历整颗二叉树转化先遍历完根节点后,在先序遍历来遍历左子树、右子树。而在先序遍历左/右子树时,我们可以将左子树和右子树当成一颗完整的二叉树,重复上述的过程,直到为空才结束。

  在求解问题时,我们发现可以将主问题可以分解为若干个相同的子问题,而这些子问题同样都可以分解为若干个相同的子子问题,不断重复。换句话说,假设我们可以通过一个方法f(可以将f想象成数学中求解问题的函数表达式f(x))来求解主问题,而主问题所转化出的子问题同样可以通过方法f来解决(而子问题所衍生出的子子问题同样可以通过方法f来解决,不断重复下去),从而实现函数自己调用自己!当面临这种情况时,即可使用递归算法来解决问题。

1.3 如何快速编写正确的递归代码?

  1. 找到相同的子问题,该过程决定了函数头的设计(即函数参数需要传哪些)
  2. 将其中一个子问题进行分析,在此过程中将递归函数当作一个黑盒,该函数能完成我们所赋予的功能。该过程决定了函数的函数体。
  3. 最后当然是返回值了。在何种情况下,递归结束。

下面以二叉树的先序遍历为例进行分析:
  首先先序遍历过程:根、左子树、右子树。而左子树和右子树显然是一个相同的子问题(左子树和右子树还可以继续细分下去,都行相同子问题,就不多说了)。所有我们需要给函数传一个根节点参数,用于分割整颗二叉树

void TreePrev(TreeNode* root)//函数头

  现在我们赋予了该递归函数功能:先序遍历二叉树。现在我们取其中一个子问题进行分析,首先我们需要遍历根节点,然后可以通过该递归函数来实现左子树、右子树的先序遍历了,即:

cout << root -> val <<" ";//根节点
//我们赋予了函数TreePrev先序遍历二叉树的功能,所有我们可以把该函数当作一个黑盒。
//只要我们将二叉树的根节点传入该黑盒,便可实现这颗二叉树的先序遍历。(实际该过程可以由画递归展开图验证,由于篇幅问题博主就不多说了)
TreePrev(root -> left);//遍历左子树
TreePrev(root -> right);//遍历右子树

  最后就是递归什么时候结束了。显然当根节点未空指针时,递归结束。此时返回空即可

if(root == nullptr)
	return;

所以整体代码就是:

void TreePrev(TreeNode* root)//函数头
{
	if(root == nullptr)
		return;
	cout << root -> val <<" ";//根节点
	//我们赋予了函数TreePrev先序遍历二叉树的功能,所有我们可以把该函数当作一个黑盒。
	//只要我们将二叉树的根节点传入该黑盒,便可实现这颗二叉树的先序遍历。(实际该过程可以由画递归展开图验证,由于篇幅问题博主就不多说了)
	TreePrev(root -> left);//遍历左子树
	TreePrev(root -> right);//遍历右子树	
}

二、力扣相关笔试题解析

面试题 08.06. 汉诺塔问题

在这里插入图片描述
【分析】:
 题目指让A柱子中的N个圆盘(自下而上降序)借助B转移到C柱子上,并且保存顺序不变。所以我们可以考虑将A柱子上的后N-1个圆盘当作一个整体移到B上,然后将A中剩余的最后一个圆盘移到C上;最后在将B盘上的所有圆盘当作一个整体移到C上即可达成目的!
 在题目的分析过程中,出现将A上的N-1个圆盘移到B上,将B上的N-1个圆盘移到C上的过程。而这两个步骤显然是一个递归子问题(将一个柱子上的N个圆盘,借助另一个柱子转移到第三个柱子)。由于该过程中是具体圆盘在3根柱子间的移到,所以函数头因有4个参数:柱A、柱B、柱C、移到圆盘个数!!

 由于该过程中,我们通过递归函数(黑盒)将A柱上的N-1个圆盘移到B柱上、将A柱上的剩余的最后一个圆盘移到C柱、通过递归函数(黑盒)将B中的所有圆盘移到C柱。所以函数体为:

//_hanota为我们待实现的递归函数,该函数的功能是将将一个柱子上的N个圆盘,借助另一个柱子转移到第三个柱子
//所以将该函数想成一个黑盒,只需向该函数传递正确参数,即可实现对应的功能!(具体由函数展开图证明)
//先将A中前num-1个圆盘借助C移到B
_hanota(A, C, B, num - 1);
//将A剩余的最后一个元素移到C柱子
C.push_back(A.back());
A.pop_back();
//将B上的num-1个圆盘借助A移到C柱子上
_hanota(B, A, C, num - 1);

 最后返回值就不多说了,A中只有一个圆盘时,无需解决B,直接将圆盘从A移到C上。

【下面是其中一个子问题的圆盘转移过程图解】:
在这里插入图片描述
【代码编写】:

class Solution {
public:
    void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
        _hanota(A, B, C, A.size());
    }
    void _hanota(vector<int>& A, vector<int>& B, vector<int>& C, int num) 
    {
        if(num == 1)
        {
            C.push_back(A.back());
            A.pop_back();
            return;
        }
        //先将A中前num-1个圆盘借助C移到B
        _hanota(A, C, B, num - 1);
        //将A剩余的最后一个元素移到C柱子
        C.push_back(A.back());
        A.pop_back();
        //将B上的num-1个圆盘借助A移到C柱子上
        _hanota(B, A, C, num - 1);
    }
};

21. 合并两个有序链表

【题目】:
在这里插入图片描述
【解析】:
 这道题目的就是将两个升序的链表合并成一个升序链表,并返回新链表的头节点。
 在本题中,我们可以先选出两链表中节点值较小的节点,然后想办法将该链表中剩下的节点和另一条链表合并为升序链表;最后将最开始选出的较小节点的next指向该新合并的新节点即可解决问题。显然将该链表中剩下的节点和另一条链表合并为升序链表就是一个递归子问题,所以可以采用递归方式解决问题。
 由于递归子问题是合并两个有序链表,所有函数头传的参数为待合并的两个链表头节点。至于函数体,我们只需挑选出原来两链表中头节点值较小的节点作为新链表的头节点,然后通过递归函数将剩下的元素和另一个链表合并,最后将新链表的头节点的next指向合并链表的头节点。当链表节点为空时,递归结束。

【代码解析】:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    	//特殊处理
        if (list1 == nullptr)
            return list2;
        if (list2 == nullptr)
            return list1;
         
        if (list1->val < list2->val)
        {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }
        else
        {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

LCR 024. 反转链表

在这里插入图片描述
反转链表我们可以将后n-1个节点反转(并返回反转后链表的头节点),然后在将原链表的头结合和新链表链接即可。
在这里插入图片描述

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr || head->next == nullptr)
            return head;
        ListNode* newhead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newhead;
    }
}

24. 两两交换链表中的节点

在这里插入图片描述
这道题本质上和反转链表类似,我们可以先将原链表头两个节点反转,然后利用递归子问题将后续所有节点当成一个完成链表,完成两两交换链表中的节点。最后将交换后的链表和原链表的头两个数据链接即可。(如果链表为空,或只有一个元素,则递归结束)

在这里插入图片描述

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return head;
        ListNode* newhead = head->next;
        head->next = swapPairs(newhead->next);
        newhead->next = head;
        return newhead;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】list介绍

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. list介绍2. list的构造3. ist iterator的使用4. capacity5. element access6. modifiers7. 迭代器失效8. Operations8.1 reverse8.2 sort8.3 unique8.4 splice 1. list介绍 list是可以在常数范围内在任意位置进行插…

关闭搜狗输入法的输入框广告

使用搜狗输入法输入内容时&#xff0c;下面总是会有广告显示 可以通过如下方式关闭该广告显示 通过以上的设置&#xff0c;可以发现输入框不会再显示广告了

MySQL(常用函数、多表查询)

文章目录 1.数据库函数1.count函数案例答案count&#xff08;*&#xff09;与count&#xff08;列&#xff09;的区别 2.sum函数案例答案 3.avg函数案例答案 4.max/min函数案例答案 5.group by 分组统计案例答案 6.字符串相关函数演示练习 7.数学相关函数演示 8.日期相关函数演…

即时配送行业吸引百万骑手就业,这个行业真的赚钱吗?

互联网浪潮的影响下&#xff0c;人们的就业模式开始呈现出多元化的发展趋势&#xff0c;新生代劳动者更加偏爱自由灵活的工作模式。在这样的背景下&#xff0c;同城配送服务的诞生&#xff0c;就为大量骑手创造了灵活的就业机会。据数据平台报告显示&#xff0c;2023年我国外卖…

linux Centos7 部署 nodejs服务

nodejs服务要有nodejs环境。所以要先安装nodejs 不会安装的可以看 Centos7 安装 npm 学习 安装pm2 cnpm install pm2 -g, 查看pm2是否安装成功 pm2 -v,如果报错&#xff0c;升级node版本 进入node项目目录,安装项目依赖 cnpm install 创建pm2任务 [rootlocalhost serv…

手写简易操作系统(二十)--实现堆内存管理

前情提要 前面我们实现了 0x80 中断&#xff0c;并实现了两个中断调用&#xff0c;getpid 和 write&#xff0c;其中 write 还由于没有实现文件系统&#xff0c;是个残血版&#xff0c;这一节我们实现堆内存管理。 一、arena 在计算机科学中&#xff0c;“arena” 内存管理通…

鸿蒙人才供需两旺、抓住职业“薪”机遇

鸿蒙生态崛起&#xff0c;人才需求旺盛&#xff01; 鸿蒙生态迅猛发展&#xff0c;人才供需均呈增长态势&#xff0c;鸿蒙开发工程师平均月薪显著领跑&#xff0c;显示出该领域的巨大潜力和吸引力&#xff0c;对于有志于鸿蒙开发的人才&#xff0c;这无疑是职业发展的黄金时期…

OpenHarmony实战:代码上库

前言 到达这一步好比临门一脚&#xff0c;意义很大&#xff01;您的代码被合入 OpenHarmony 平台&#xff0c;这是最后的一道关口&#xff0c;保证合入的是正确的&#xff0c;并且不会对系统造成意外。 避坑指南 1. 填写 ISSUE 和 PR 按照规范进行 ISSUE 和 PR 填写不规范会…

CentOs7.9中修改Mysql8.0.28默认的3306端口防止被端口扫描入侵

若你的服务器被入侵&#xff0c;可以从这些地方找到证据&#xff1a; 若有上述信息&#xff0c;300%是被入侵了&#xff0c;重装服务器系统以后再重装Mysql数据库&#xff0c;除了设置一个复杂的密码以外&#xff0c;还需要修改默认的Mysql访问端口&#xff0c;逃避常规端口扫描…

马斯克旗下xAI发布Grok-1.5,相比较开源的Grok-1,各项性能大幅提升,接近GPT-4!

本文原文来自DataLearnerAI官方网站&#xff1a;马斯克旗下xAI发布Grok-1.5&#xff0c;相比较开源的Grok-1&#xff0c;各项性能大幅提升&#xff0c;接近GPT-4&#xff01; | 数据学习者官方网站(Datalearner) 继Grok-1开源之后&#xff0c;xAI宣布了Grok-1.5的内测消息&…

Homebrew 镜像源配置

前言 当我们使用默认官方源时&#xff0c;经常会遇到以下问题 查看镜像配置 brew config 切换国内源 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 4.0 镜像配置 温馨提示&#xff1a;不要使用阿里云的 Homebrew 源&am…

使用CRXjs、Vite、Vue 开发 Chrome 多页面插件,手动配置 vite.config.ts 和 manifest.json 文件

一、使用CRXjs、Vite、Vue 开发 Chrome 多页面插件&#xff0c;手动配置 vite.config.ts 和 manifest.json 文件 一、创建 Vue 项目 1. 使用 Vite 创建 Vue 项目 npm create vitelatest # npm yarn create vite # yarn pnpm create vite # pnpm选择 Vue 和 TS 进入项目…

Spring IOC 容器循环依赖解决(三级缓存)

对于循环依赖的解决&#xff0c;首先得了解Spring IOC 容器的创建过程&#xff0c;在加载过程中&#xff0c;Bean 的实例化和初始化是分开的&#xff0c;所以在解决循环依赖的问题时&#xff0c;也是基于Bean 的实例化和初始化分开执行这一特点。 我们将实例化后的Bean 叫 半成…

【Web自动化】Selenium的使用(一)

目录 关于自动化测试selenium工作机制 selenium的使用selenium中常用API定位元素按id定位按名称定位按类名定位按标签名定位按CSS选择器定位按XPath定位示例 操作测试对象等待sleep休眠隐式等待显示等待 打印信息浏览器操作键盘事件鼠标事件切换窗口截图关闭浏览器 欢迎阅读本文…

Windows 11 专业版 23H2 Docker Desktop 下载 安装 配置 使用

博文目录 文章目录 Docker Desktop准备系统要求 (WSL 2 backend)在 Windows 上打开 WSL 2 功能先决条件开启 WSL 2 WSL下载安装启动配置使用镜像 Image卷积 Volumes容器 Containers 命令RedisMySQLPostGreSQL Docker Desktop Overview of Docker Desktop Docker Desktop 疑难解…

揭秘五五复制模式,助力平台用户快速裂变至百万级!

你是否时常为平台的用户增长缓慢而倍感压力&#xff1f;是否渴望找到一种方法&#xff0c;让平台用户迅速扩张&#xff0c;实现百万级用户量的突破&#xff1f;今天&#xff0c;我将为大家揭晓一种创新的商业模式——五五复制模式&#xff0c;它或许能成为你实现梦想的关键。 五…

位运算

本文用于记录个人算法竞赛学习&#xff0c;仅供参考 目录 一.n的二进制表示中第k位x 二.通过lowbit操作返回x的最后一位1 1.lowbit实现&#xff1a;x & (-x) 2. lowbit具体作用 一.n的二进制表示中第k位x n 15 &#xff08;1111&#xff09;2 操作&#xff1a;1.x …

Redis主从同步机制

一、步骤如下&#xff1a;&#xff08;全量&#xff09; 1.从服务器向主服务器发送同步命令 sync&#xff1b; 2.主数据库接收到同步命令后&#xff0c;会执行 bgsave 命令&#xff0c;在后台生成一个 rdb 文件&#xff0c;并使用一个缓冲区记录从现在开始执行的所有写命令&a…

苏州金龙新V系客车创新打造,剑指新标杆

诞生于2004年的苏州金龙V系客车在20年时间里销售了6万多辆&#xff0c;用户超过5000家&#xff0c;用户的反复选择体现了它超强的产品力。3月下旬&#xff0c;全新打造的苏州金龙新V系客车震撼登场&#xff0c;拥趸们发现&#xff0c;该系列客车在智能化、网联化及设计语言方面…

如何使用剪映专业版剪辑视频

1.操作界面功能介绍 2.时间线的使用 拖动前端后端缩减时长&#xff0c;有多个素材可以拖动调节前后顺序拼接。 分割视频 删除