【不一样的递归大法】

news2025/1/12 3:46:55

🎁递归

  • 🎅递归
    • 🦌定义
  • 🎅何时用递归:递归三板斧
    • 🦌递归=递+归
    • 🦌递归大法:三板斧
  • 🎅如何快速写出递归函数:宏观的角度
  • 🎅解题突破
    • 🦌整数序列相关
      • 🎄求阶乘
      • 🎄正序打印数字的所有位
    • 🦌数组相关
      • 🎄数组求和
    • 🦌单链表相关
      • 🎄链表反转
      • 🎄从尾到头打印链表
    • 🦌二叉树相关
      • 🎄二叉树高度
      • 🎄平衡二叉树

🎅递归

🦌定义

在函数执行过程中调用函数自身的程序设计称为递归

举个例子:

public void fib(int n){
	if(n==1){
		return;
	}
	fib(n-1);
}

在这个例子中,fib(int n)方法在自己的方法体中调用了自身,这就是递归调用。

🎅何时用递归:递归三板斧

递归的大体思路是自顶向下将规模较大的原问题不断拆分为规模相对较小的小问题,然后自底向上逐个解决小问题,最后得到原始大问题的解。

🦌递归=递+归

将递归的完整过程可分为“递”和“归”两步:

递:不断拆分问题
归:解决小问题并返回结果

递归函数依据自己不断调用自身的过程,将原始问题拆分为有限个小问题,拆分行为一直进行到递归终止条件,转而处理求解各个小问题并向上返回,最终得到原问题的解。

🦌递归大法:三板斧

由上面的理解我们可以总结出递归的条件,当一个问题满足以下全部条件时,我们即可尝试使用递归的方式去求解:

  • 原始大问题可拆分为多个小问题;
  • 拆分得到的小问题,除了数据规模与原始问题的数据规模不同之外,求解问题的思路完全一致;
  • 子问题拆分是有限的,必须存在递归终止条件:其中,递归终止条件指的是,不借助任何其他条件或函数,就可立即得到该问题的计算结果。

🎅如何快速写出递归函数:宏观的角度

那么我们确定了求解某问题需要使用递归时,如何快速确定递归函数呢?答案是:站在宏观的角度去思考问题,不要纠结于递归内部如何进行。

如何快速写出递归函数:站在宏观的角度,始终把握该函数的语义。
什么是函数的语义:该函数的定义是为了干什么,也就是该函数的目的是什么?
我们在编写递归函数的具体实现时,假设该函数是已经内置好的API,我们需要时直接调用即可。

🎅解题突破

由于递归这种程序设计技巧,巧妙的将大问题不断拆分为小问题,化整为零的特点,其在数据结构相关的很多方面都有着广泛的应用,比如简单的数字序列、数组、链表、二叉树等。下面我针对这些应用分别列出几个示例,以此验证“三板斧”的作用以及“宏观”角度的精妙:

🦌整数序列相关

🎄求阶乘

问题描述:给定一个整数n,计算得到n!,即数字n的阶乘。

三板斧:

  • 原问题可拆解:n!=n*(n-1)!;
  • n!与(n-1)!求解思路完全一致,只不过二者规模大小不同;
  • 拆分是有限的,当n=1时,n!=1,我们可直接得到计算结果;

代码:

int getFactorial(int num) {
	// base case:递归中止条件
    if (num==1){
        return 1;
    }
    // n!=n*(n-1)!,而刚好有一个函数getFactorial可以计算某个整数的阶乘,我们直接调用即可
    return num*getFactorial(num-1);
}

测试结果:
n=3,
在这里插入图片描述

🎄正序打印数字的所有位

问题描述:给定一个整数n,从最高位到最低位逐个输出对应位的数字,比如n=129,输出1 2 9。

三板斧: 假设有一个函数printNum用于逐个打印数字num的每一位

  • 原问题可拆解,比如n=129,可先输出数字12的每一位,然后打印9;
  • 子问题与原问题求解思路完全一致,只不过二者规模大小不同;
  • 拆分是有限的,当n<10时,也就是n为个位数时,可直接输出n;

代码:

void printNum(int num) {
	// base case:输入整数为个位数时,可直接输出
    if (num<10){
        System.out.print(num+" ");
        return;
    }
    // 逐个打印num的个位之外的所有数字
    printNum(num/10);
    // 打印num的个位数
    System.out.print(num%10+" ");
}

测试结果:
在这里插入图片描述

🦌数组相关

🎄数组求和

问题描述:给定一个整数数组arr,计算并返回该数组的所有元素之和。

三板斧: 设数组长度为n,该问题可表示为sum(arr,n-1)

  • 原问题可拆解,比如sum(arr,n-1)=arr[n-1]+sum(arr,n-2),即数组元素之和为数组前[0,n-2]元素之和+元素arr[n-1];
  • 子问题与原问题求解思路完全一致,只不过二者规模大小不同;
  • 拆分是有限的,当n=1时,也就是数组只有一个元素时,元素本身就是该数组元素之和;

代码:

int sum(int[] arr, int lastIndex) {
    if (lastIndex==0){
        return arr[0];
    }
    return sum(arr,lastIndex-1)+arr[lastIndex];
}

测试结果:
在这里插入图片描述

🦌单链表相关

🎄链表反转

问题描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
原题链接https://leetcode.cn/problems/reverse-linked-list/

三板斧: 该问题可表示为 reverseList(ListNode head)

  • 原问题可拆解,比如reverseList(ListNode head)过程可拆分为两个子过程,1)首先反转以head.next节点为头节点的子链表,即调用reverseList(head.next),返回反转后的子链表的头节点;2)然后将head节点链接在1)中得到的子链表链尾即可即可;
  • 子问题与原问题求解思路完全一致,只不过二者规模大小不同;
  • 拆分是有限的,这里有两个终止条件,满足二者中的一个即可停止递归:1)当head=null,即传入链表为空时,直接返回null;2)head.next=null,即此时传入链表为单节点链表,无论正序还是逆序,该链表都为此节点本身,所以直接返回该节点即可;

代码:

public ListNode reverseList(ListNode head) {
    // 递归
    // 终止条件
    if(head==null){
        return null;
    }
    if(head.next==null){
        return head;
    }
    // 其他情况
    ListNode second=head.next;
    head.next=null;
    ListNode newHead=reverseList(second);
    second.next=head;
    return newHead;
}

测试结果:
在这里插入图片描述

🎄从尾到头打印链表

问题描述:输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
原题链接https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/

三板斧: 该问题可表示为 reversePrint(ListNode head)

  • 原问题可拆解,比如reversePrint(ListNode head)过程可拆分为两个子过程,1)首先逆序打印以head.next节点为头节点的子链表,即调用reversePrint(headnext);2)然后打印head节点即可;
  • 子问题与原问题求解思路完全一致,只不过二者规模大小不同;
  • 拆分是有限的,这里有两个终止条件,满足二者中的一个即可停止递归:1)当head=null,即传入链表为空时,直接返回空数组;2)head.next=null,即此时传入链表为单节点链表,无论正序还是逆序打印都为该节点本身,所以直接返回该节点组成的数组即可;

代码:

public int[] reversePrint(ListNode head) {
    // 递归,语义:输入链表头节点,逆序打印链表元素
    if(head==null){  // 空链表
        return new int[0];
    }
    if(head.next==null){  // 单节点链表
        return new int[]{head.val};
    }
    ListNode second=head.next;
    head.next=null;
    int[] last=reversePrint(second);
    int[] res=Arrays.copyOf(last,last.length+1);
    res[res.length-1]=head.val;
    return res;
}

测试结果:
在这里插入图片描述
注意此题使用递归并不是最优解法,这里只是表达递归的广泛应用。

🦌二叉树相关

🎄二叉树高度

问题描述:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。
原题链接https://leetcode.cn/problems/maximum-depth-of-binary-tree/

三板斧: 该问题可表示为 maxDepth(TreeNode root)

  • 原问题可拆解,比如maxDepth(TreeNode root) 过程可拆分为三个子过程,1)首先分别计算得到左右子树的高度,即分别调用maxDepth(root.left) 、maxDepth(root.left) ;2)然后取左右子树高度中的最大值,即Max(leftHight,rightHight);3)最后将2)得到的结果+1,即加上root根节点的高度
  • 子问题与原问题求解思路完全一致,只不过二者规模大小不同;
  • 拆分是有限的,这里的递归终止条件为root==null,即传入空树时,则说明高度为0,直接返回0即可;

代码:

public int maxDepth(TreeNode root) {
	// 递归终止条件
    if(root==null){
        return 0;
    }
    // 其他情况
    return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}

测试结果:
在这里插入图片描述

🎄平衡二叉树

问题描述:给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 ,如下图所示为一棵合法的平衡二叉树。
原题链接https://leetcode.cn/problems/balanced-binary-tree/
在这里插入图片描述

三板斧: 该问题可表示为 isBalanced(TreeNode root),先来分析一下,如何确定以root为根节点的二叉树为一棵平衡树呢?我们需要去计算其左右子树的高度,当高度差的绝对值超过1时说明该树不是平衡二叉树,反之如果所有子树都满足其左右子树高度差不超过1,则说明以root为根节点的整棵树为平衡二叉树。所以这里需要借助到上一题求解二叉树高度的函数 maxDepth(TreeNode root)。

  • 原问题可拆解,比如isBalanced(TreeNode root)过程可拆分为两个子过程,1)首先判断root为根节点的二叉树是否满足平衡二叉树左右子树高度差绝对值不超过1的限制;2)如果1)满足,则判断root的左右子树是否都满足平衡二叉树的要求;
  • 子问题与原问题求解思路完全一致,只不过二者规模大小不同;
  • 拆分是有限的,这里的递归终止条件为root==null,即传入空树,我们知道空树天然是一棵平衡二叉树,故直接返回true即可;

代码:

public boolean isBalanced(TreeNode root) {
    // 递归终止条件:空树天然为一棵平衡树
    if(root==null){
        return true;
    }
    // 分别计算左右子树的高度
    int leftHeight=maxDepth(root.left);
    int rightHeight=maxDepth(root.right);
    // 如果root的左右子树高度差绝对值超过1则说明root树不是平衡树
    if(Math.abs(leftHeight-rightHeight)>1){
        return false;
    }
    // 否则判断左右子树是否都是平衡树
    return isBalanced(root.left)&&isBalanced(root.right);
}

public int maxDepth(TreeNode root) {
    if(root==null){
        return 0;
    }
    return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}

测试结果:
在这里插入图片描述

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

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

相关文章

一文了解什么是NFT

一、什么是NFT NFT 是我们可以用来代表独特物品所有权的代币。他们让我们对艺术品、收藏品甚至房地产等事物进行代币化。资产的所有权由以太坊区块链保护——没有人可以修改所有权记录或复制/粘贴新的 NFT。 NFT 代表不可替代的代币。Non-fungible 是一个经济学术语&#xff…

伪操作和混合汇编

目录 一、伪操作: 二、C和汇编的混合编程 三、ATPCS协议(ARM-THUMB Procedure Call Standard) 一、伪操作: 不会生成代码&#xff0c;只是在编译之前告诉编译器怎么编译 GNU的伪操作一般都以‘.’开头 .global symbol 将symbo…

WEB 安全,浅谈 XSS 攻击(附简单实例)

什么是 XSS XSS(Cross-Site-Scripting)&#xff0c;跨站脚本攻击&#xff0c;因为缩写和 CSS 重叠&#xff0c;被别人抢先了&#xff0c;所以只能叫做 XSS。 攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。若受害者运行这些恶意代码&#xff0c;攻击者就可以突破网站…

电感和磁珠有哪些区别?

由于电感和磁珠&#xff0c;很多人会容易认错&#xff0c;本期内容就讲讲&#xff0c;有哪些相似之处&#xff01; 磁珠与电感不仅在外形上相似&#xff0c;他们在功能上也存在很多相似之处&#xff0c;甚至有时候磁珠和电感可以相互代替。但是磁珠与电感也不完全等同&#xff…

开关电源环路稳定性分析(09)——环路补偿六步法

大家好&#xff0c;这里是大话硬件。 我们来回顾一下前面8讲的内容&#xff0c;主要对下面的知识点进行了分析&#xff1a; 系统框图 反馈环节传递函数 功率级传递函数 PWM级传递函数 传递函数计算 如果我们把开关电源看成是不同的电路模块拼接而成&#xff0c;现在已经知…

c++模板认识以及使用

我们都知道c有函数重载的概念&#xff0c;比如我们写一个相加的函数&#xff0c;以整数为例&#xff0c;我们大概率是这样写&#xff1a; int Add(int x,int y) {return xy; } 并且我们知道c函数重载的概念&#xff0c;于是我们若是想写double类型&#xff0c;float类型&…

centos7安装php7.1 验证码GD库扩展

php安装不推荐使用源码包安装&#xff0c;版本太多。线上从php5.x一直升级到php7.1 程序可兼容 配置yum源 rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm 添加epel…

使用Stabel Diffusion

Stabel Diffusion是由CompVis、stabel AI和LAION的研究人员和工程师创建的文本到图像的潜在扩散模型。它由来自LAION-5B数据库子集的512x512图像进行训练。LAION-5B是目前最大的、可自由访问的多模态数据集。 在这篇文章中&#xff0c;将介绍如何使用diffusion库实现Stabel Di…

MAC安装jmeter以及JDK配置

JDK配置 一、检查是否安装了jdk 打开终端&#xff0c;输入java -version校验jdk是否安装 我这已经安装了版本1.8.0版本的 若没有安装&#xff0c;则去官网下载jdk并安装 1、jdk下载官网&#xff1a;Java Downloads | Oracle tar包或者dmg&#xff0c;二者区别在于&#xff1a;…

7、GPIO输入按键检测(外部中断)

目录 0x01、简介 0x001、EXTI 简介 0x002、EXTI 功能框图 0x003、中断/事件线 0x02、硬件设计 0x03、相关库函数 0x0001、外部中断初始化 0x0002、外部中断GPIO引脚选择 0x04、编写函数 0x001、按键外部中断初始化 0x002、中断函数 0x05、源程序下载地址 0x01、简介…

Android插件化换肤原理—— 布局加载过程、View创建流程、Resources 浅析

前言 继上次 WebView 干货分享后&#xff0c;本次将分享下自己在探索学习 App 换肤功能过程中的相关知识&#xff0c;着重分享换肤的原理以及实现思路。 由于篇幅原因分为两篇博客&#xff0c;本文主要分析了 Android 布局加载流程&#xff0c;下一篇将具体讲解插件化换肤实现…

Linux——标准IO

文件的基础 概念&#xff1a;一组相关数据的有序集合 文件的类型&#xff1a; 常规文件-r 目录文件-d 字符设备文件-c&#xff1a;键盘 块设备文件-b:U盘 磁盘 管道文件-p 套接字文件-s 符号链接文件-I&#xff1a;快捷方式 标准I/O 流 file 标准IO用一个结构体类型来保存打…

数据质量管理—理论大纲与实践(B站)

0、背景 故事的开头&#xff0c;是一位业务部门的同事找到我们&#xff0c;咨询了一个经典问题&#xff1a; 「需求方经常说我们做的报表看起来数据不准&#xff0c;有什么办法吗&#xff1f;」 为了解释这个问题&#xff0c;我以我们团队在数据质量管理中积累下来的方法&am…

决策树和随机森林的python实现

文章目录决策树实现方法测试更好地展示结果调参调整max_depthscoring利用GridSearchCV确定最佳max_depthmin_samples_splitmin_impurity_decreasemax_features多参数同时选优采用最优参数特征重要性排序随机森林测试调参n_estimators调整max_depth调整max_features调整min_samp…

黑马Hive+Spark离线数仓工业项目--数仓事实层DWB层构建(2)

工单事实指标构建 目标&#xff1a;实现DWB层工单事实指标表的构建 实施 建表 抽取 安装事实指标需求分析 目标&#xff1a;掌握DWB层安装事实指标表的需求分析 路径 - step1&#xff1a;目标需求 - step2&#xff1a;数据来源 实施 目标需求&#xff1a;基于设备安装信…

Python【继承】复写使用父类成员

继承&#xff1a;继承就是一个类&#xff0c;继承另外一个类的成员变量&#xff08;属性&#xff09;和成员方法 继承的作用&#xff1a;子类通过继承父类的属性和方法&#xff0c;在调用的时候&#xff0c;除了可以使用子类自身的成员方法和属性外&#xff0c;还可以使用父类…

模型不达标调整

一 、模型不达标调整 模型构建就是——科学的研究问题的数学表达&#xff1b;比如线性回归模型中的模型公式。 在进行建模时&#xff0c;很多同学会遇到模型不达标的问题&#xff0c;这种情况很常见&#xff0c;通常需要进行模型不达标的调整。 模型不好如何处理 模型拟合不…

使用扩散模型训练文本贴图

🍿*★,*:.☆欢迎您/$:*.★* 🍿 整个代码除了数据处理 其他是借鉴而来 那么说说 数据处理 采取的是使用pil 将某种字体中文写入到图片上去 而后生成图,最后进行加噪生成数据集 简单的利用生成器进行 batch 训练了100epoch 可以简单的生成一些带有文字的图 import numpy a…

数据可视化①:dashboard展示大学生就业现状

大学生就业是和我们息息相关的话题&#xff0c;每一位大学生都关注着&#xff0c;我们常常在网络上看到有关大学生就业的话题&#xff0c;比如毕业季的一些讨论。在大一的创新创业课中&#xff0c;我们也了解到自己所学的专业和以后如何就业&#xff0c;往哪方面就业。但我们了…

linux下安装java环境(小D课堂)

VirtualBox下载安装&#xff1a; 官网去下载就好了。 然后安装就可以了。 然后我们去安装我们的centos7. 然后我们可以从这里选择&#xff0c;然后进行下载。 这里我就用它默认的大小&#xff0c;后面还可以去改。 然后这个虚拟机我们就创建完成了。 我们去双击打开它&#xff…