递归、搜索与回溯算法(专题一:递归)

news2024/11/14 1:16:39

往期文章(希望小伙伴们在看这篇文章之前,看一下往期文章)

(1)递归、搜索与回溯算法(专题零:解释回溯算法中涉及到的名词)【回溯算法入门必看】-CSDN博客

接下来我会用几道题,来让同学们利用我在专题零中提到的递归的宏观思想来解决这些题目。

目录

1. 汉诺塔

2. 合并两个有序链表

3. 反转链表

4. 两两交换链表中的结点

5. 快速幂

解法一  暴力循环

解法二  不断拆分

解法三  利用了二进制的特点


1. 汉诺塔

这道题可以说是递归最经典的题目之一,接下来我们就从这道题入手,重新认识一下递归是什么?

力扣题目链接

解析:

 

总结:我们想知道n个盘子的次数,记住了,在求解f(n)的时候,我们直接默认f(n - 1)已经完了就可以了!这个在前面已经解释过了,在此不再鳌述。我们将n-1个盘子当作一个整体:这就是类似于分治求解子问题的思想

那么当由3个盘子时,就有公式:f(3,x,y,z) = f(2,x,z,y),x->z,f(2,y,x,z);当有4个盘子时,就有公式:f(4,x,y,z) = f(3,x,z,y),x->z,f(3,y,x,z).从而推出f(n,x,y,z) = f(n,x,z,y),x->z,f(n,y,x,z)!

综上所述:也就是说我们想移动n个盘子,就需要先移动n - 1个盘子;移动n - 1个盘子,就需要先移动n - 2个盘子 ………………;移动2个盘子,就必须先移动1个盘子;

(1)而移动1个盘子就是递归的终止条件

(2)而n个盘子变成n - 1个盘子就是递归的大问题变成小问题的方法

宏观角度分析递归:

再来回顾,宏观的细节

  • 一:不要在意递归的细节展开图
  • 二:把递归的函数当成一个“黑盒”
  • 三:我们的无条件的相信“黑盒”可以为我们解决当前的子问题(再次强调,递归中的每个子问题的解题步骤都是一样)

具体构建一个递归算法:

(1)创建函数头 ——> 从重复子问题入手,将x柱子上的盘子,借助y柱子转移到z柱子上。从而有函数头为:dfs(int x,int y,int z,int n); x、y和z代表了柱子,n代表现在多少个盘子需要移动。x是开始柱,y是中转柱,z是目的柱。

(2)确定函数体 ——> 无条件相信他能帮我们解决每一个子问题的。

重复的子问题如下:

  • 如从 x 号柱子将n - 1个盘子借助 z 号柱子移动到 y 号柱子上
  • 将 x 号柱子将最后一个盘子移动到 z 号柱子上
  • 将 y 号柱子上的n - 1个盘子移动到 z 号柱子上

从而有函数体为:

① dfs(x,z,y,n - 1); ② x.back -> z; ③ dfs(y,x,z,n - 1);

(3)递归出口:当剩下一个盘子的时候,也就是n == 1时。x.back -> z即可。

    //宏观角度分析递归
    //1.创建函数头
    //2.确定函数体,无条件相信他能帮我们解决每一个子问题的
    //3.递归出口
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        dfs(A,B,C,A.size());
    }

    public void dfs(List<Integer> x,List<Integer> y,List<Integer> z,int size){
        //递归出口
        if(size == 1){
            z.add(x.remove(x.size() - 1));
            return;
        }

        //封装一个小黑盒
        //将x柱上的size - 1个盘子通过z柱子移动到y柱子上
        dfs(x,z,y,size - 1);
        //将a柱上的最后一个盘子移动到c柱子上
        z.add(x.remove(x.size() - 1));
        //将b柱上的size - 1个盘子通过a柱子移动到c柱子上
        dfs(y,x,z,size - 1);
    }

2. 合并两个有序链表

力扣题目链接

解析:
(1)递归函数的含义:交给你两个链表的头结点,你帮我把它们合并起来,并且返回合并后的头结点。
(2)函数体:选择两个头结点中较⼩的结点作为最终合并后的头结点,然后将剩下的链表交给递归函数去处理。
(3)递归出⼝:当某⼀个链表为空的时候,返回另外⼀个链表。 

    // 法一:递归法
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 递归出口:当l1或者l2为空,就返回另一个不为空的链表
        if (l1 == null)
            return l2;
        if (l2 == null)
            return l1;
        //如果l1的值小于l2,那么就让l1作为头结点,剩下的结点继续比较
        if (l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }
        else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }

3. 反转链表

力扣题目链接

 

解析: 

(1)递归函数的含义:交给你⼀个链表的头指针,你帮我逆序之后,返回逆序后的头结点。
(2)函数体:先把当前结点之后的链表逆序,逆序完之后,把当前结点添加到逆序后的链表后⾯即可。(相同的子问题)
(3)递归出⼝:当前结点为空或者当前只有⼀个结点的时候,不⽤逆序,直接返回。

    public ListNode reverseList(ListNode head) {
        return dfs(head);
    }
    public ListNode dfs(ListNode h){
        //递归出口:直接到找最后一个结点,类似后序遍历
        if(h == null || h.next == null){
            return h;
        }
        //找最后一个结点
        ListNode newNode = dfs(h.next);
        //从倒数第二个结点开始
        h.next.next = h;
        h.next = null;

        return newNode;
    }

4. 两两交换链表中的结点

力扣题目链接

解析:

(1)递归函数的含义:交给你⼀个链表,将这个链表两两交换⼀下,然后返回交换后的头结点。
(2)函数体:先去处理⼀下第⼆个结点往后的链表,然后再把当前的两个结点交换⼀下,连接上后⾯处理后的链表。(相同的子问题)
(3)递归出⼝:当前结点为空或者当前只有⼀个结点的时候,不⽤交换,直接返回。 

    //后序遍历
    public ListNode swapPairs(ListNode head) {
        //链表中节点的数目在范围 [0, 100] 内
        //如果只剩下一个结点就没必要交换了,因为是两两交换
        if(head == null || head.next == null){
            return head;
        }
        //先去交换在我之后之后的结点
        ListNode newNode = swapPairs(head.next.next);
        ListNode ret = head.next;
        head.next = newNode;
        ret.next = head;
        return ret;
    }

 要先修改后面结点,再能修改当前结点,很类似后序遍历的方式。

5. 快速幂

力扣题目链接

解法一  暴力循环

    public double myPow(double x, int n) {
        double ret = 1;
        int tmp = n;
        if(n < 0) n = -n;
        for(int i = 1;i <= n;i++){
            ret *= x;
        }
        if(tmp < 0) ret = 1.0 / ret;
        return ret;
    }

暴力循环可以解决较小的幂,当幂的值比较大就会报超时的错误!!!

所以有了如下两个改进方案:

 

解法二  不断拆分

(1)递归函数的含义:求出 x 的 n 次⽅是多少,然后返回。
(2)函数体:先求出 x 的 n / 2 次⽅是多少,然后根据 n 的奇偶,得出 x 的 n 次⽅是多少。
(3)递归出⼝:当 n 为 0 的时候,返回 1 即可 。

    //第二种方法:
    public double myPow(double x, int n) {
        return n < 0 ? 1.0 / pow(x,-n) : pow(x,n);
    }

    public double pow(double x,int n){
        if(n == 0)
            return 1.0;
        double tmp = pow(x,n/2);
        return n % 2 == 0 ? tmp * tmp : tmp * tmp * x;
    }

解法三  利用了二进制的特点

例如算a^{11}:

分解:a^{11}=a^{8+2+1} = a^{8}\times a^{2}\times a^{1}

(1)而a^{8}a^{2}a^{1}是倍乘关系,所以算a^{11}不用11个a相乘,而是 a^{2}=a^{1}\times a^{1} 得到,a^{4} = a^{2} \times a^{2},而 a^{8} = a^{4} \times a^{4}得到。

(2)那么问题来了,如何将11分解成 11 = 8 + 2 + 1这样的倍乘关系呢?

答:11_{10} = 1011_{2} = 2^{3}+2^{1}+2^{0} = 8+2+1

(3)这里因为二进制的11中第三位为0,所以没有 2^{2} = 4,如果跳过4呢?1011中的0,就是需要跳过的 4。

(4)11_{10} = 1011_{2} = 2^{3}+2^{1}+2^{0} = 8+2+1

推算乘积:从低位往高位处理1011(右移一次,就把刚处理的低位移走了)

  • 1011,处理末尾的1:计算a。 res=a
  • 1011,处理第2个1:计算a^{2}。 res = res\times a^{2} = a^{1+2} ;
  • 1011,处理0:跳过a^{4}
  • 1011,处理1:计算a^{8}。 res=res\times a^{8}=a^{1+2+8}
    //第三种方法:
    public double myPow(double x, int n) {
        double base = x;
        int tmp = n;
        double res = 1;
        if(n < 0) n = -n;
        while(n != 0){
            if((n & 1) != 0) //如果n的最后一位是1,表示这个地方需要乘
                res*=base;
            base*=base;//推算乘积,在解析有说明
            n>>=1;//n右移一位,把刚处理过n的最后一位去掉
        }
        if(tmp < 0) res = 1.0 / res;
        return res;
    }

注:解法三的速度比解法一快了许多,但是相比于解法二还是慢了一些,导致超时错误。

 

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

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

相关文章

【开源】基于JAVA语言的教学资源共享平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 类图设计3.3 数据库设计3.3.1 课程档案表3.3.2 课程资源表3.3.3 课程作业表3.3.4 课程评价表 四、系统展…

WebSocket协议、与HTTP对比

WebSocket 也可前往本人的个人网站进行阅读 WebSocket 和 HTTP WebSocket和HTTP协议一样&#xff0c;都是基于TCP协议实现的应用层协议。 HTTP协议通常是单边通信&#xff0c;主要用于传输静态文档、请求-响应通信&#xff0c;适用于Web浏览器加载网页、API调用等。然而Web…

C++类与对象【运算符重载】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;C从基础到进阶 &#x1f384;1 运算符重载&#x1f33d;1.1 加号运算符重载&#x1f33d;1.2 左移运算符重载&#x1f33d;1.3 递增运算符重载&#x1f33d;1.4 赋值运算符重载&#x1f33…

『MySQL快速上手』-⑩-索引特性

文章目录 1.索引的作用2.索引的理解建立测试表插入多条记录查看结果 2.1 MySQL与磁盘交互的基本单位2.1 为何IO交互要是 Page2.3 理解单个Page2.4 理解多个Page2.5 页目录2.6 单页情况2.7 多页情况2.8 B vs B2.9 聚簇索引 vs 非聚簇索引非聚簇索引聚簇索引 3.索引操作3.1 创建主…

C#操作pdf之使用itext实现01-生成一个简单的table

创建.net 8控制台项目 安装itext <PackageReference Include"itext" Version"8.0.2" /><PackageReference Include"itext.bouncy-castle-adapter" Version"8.0.2" /><PackageReference Include"itext.bouncy-cast…

操作系统实验报告

目录 目录 实验一 一、实验结果 实验二 使用信号量实现进程互斥与同步 一、实验结果 1. 使用信号量实现有限缓冲区的生产者和消费者问题 2. 使用信号量实现读进程具有优先权的读者和写者问题 实验三 死锁和预防 一、实验要求 二、实验内容 三、实验结果 实验四 内…

list上

文章目录 初步了解list面试题&#xff1a;为什么会有list&#xff1f;vector的缺点&#xff1a;vector、list优点 list结构迭代器的分类list的简单运用insert、erase、迭代器失效&#xff08;和vector的区别&#xff09;erase class和structlist的迭代器为什么这个迭代器的构造…

go 语言(九)----struct

定义一个结构体 type Book struct {title stringauth string }结构体使用 package mainimport "fmt"//定义一个结构体 type Book struct {title stringauth string }func main() {var book1 Bookbook1.title "Golang"book1.auth "zhang3"fmt…

PSoc62™开发板之i2c通信

实验目的 使用模拟i2c接口读取温湿度气压模块BME280数据 实验准备 PSoc62™开发板温湿度气压模块BME280公母头杜邦线 板载资源 本次实验是通过模拟i2c时序的方式来进行通信&#xff0c;理论上可以有非常多的方式配置i2c引脚&#xff0c;不像硬件i2c那样芯片出厂引脚已经固…

Apache Doris (六十七): DataX DorisWriter - (1) - 源码编译及集成

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录

GetShell的姿势

0x00 什么是WebShell 渗透测试工作的一个阶段性目标就是获取目标服务器的操作控制权限&#xff0c;于是WebShell便应运而生。Webshell中的WEB就是web服务&#xff0c;shell就是管理攻击者与操作系统之间的交互。Webshell被称为攻击者通过Web服务器端口对Web服务器有一定的操作权…

怎么移除WordPress后台工具栏的查看站点子菜单?如何改为一级菜单?

默认情况下&#xff0c;我们在WordPress后台想要访问前端网站&#xff0c;需要将鼠标移动到左上角的站点名称&#xff0c;然后点击下拉菜单中的“查看站点”才行&#xff0c;而且还不是新窗口打开。那么有没有办法将这个“查看站点”子菜单变成一级菜单并显示在顶部管理工具栏中…

数据分析-Pandas如何整合多张数据表

数据分析-Pandas如何整合多张数据表 数据表&#xff0c;时间序列数据在数据分析建模中很常见&#xff0c;例如天气预报&#xff0c;空气状态监测&#xff0c;股票交易等金融场景。数据分析过程中重新调整&#xff0c;重塑数据表是很重要的技巧&#xff0c;此处选择Titanic数据…

Qt拖拽组件与键盘事件

1.相关说明 1.设置widget或view的拖拽和放置模式函数setDragDropMode参数说明&#xff0c;NoDragDrop(无拖拽和放置)、DragOnly(只允许拖拽)、DropOnly(只允许放置)、DragDrop(允许拖拽和放置)、InternalMove(只移动不复制) 2.设置widget或view的放置动作函数setDefaultDropAct…

小周带你读论文-2之“草履虫都能看懂的Transformer老活儿新整“Attention is all you need(2)

书接前文&#xff1a;小周带你读论文-2之"草履虫都能看懂的Transformer老活儿新整"Attention is all you need(1) (qq.com) 上文书说到为什么我们要用casual-decoder架构&#xff0c;把Transformer的左边给省略了&#xff0c;于是得到下图这样的架构 上图是GPT-1的模…

leetcode2312卖木头块

其实不难&#xff0c;主要是你得一眼看出来这个问题缩小规模然后就可以用DP来做了 using ll long long; class Solution { public:long long sellingWood(int m, int n, vector<vector<int>>& prices) {ll f[m10][n10];ll p[m10][n10];memset(p,0,sizeof p)…

AI时代—ChatGPT-4.5的正确打开方式

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言4.5key价格泄漏ChatGPT4.0使用地址ChatGPT正确打开方式最新功能语音助手存档…

数据结构——Java实现栈和队列

一、栈 Stack 1.特点 &#xff08;1&#xff09;栈是一种线性数据结构 &#xff08;2&#xff09;规定只能从栈顶添加元素&#xff0c;从栈顶取出元素 &#xff08;3&#xff09;是一种先进后出的数据结构&#xff08;Last First Out&#xff09;LIFO 2.具体实现 Java中可…

win系统环境搭建(十二)——Windows系统下使用docker安装redis

windows环境搭建专栏&#x1f517;点击跳转 win系统环境搭建&#xff08;十二&#xff09;——Windows系统下使用docker安装redis 文章目录 win系统环境搭建&#xff08;十二&#xff09;——Windows系统下使用docker安装redis1.创建文件夹2.docker-compose.yaml配置文件3.red…

《WebKit 技术内幕》之四(1): 资源加载和网络栈

第四章 资源加载和网络栈 使用网络栈来下载网页和网页资源是渲染引擎工作的第一步 1.WebKit 资源加载机制 1.1 资源 网页本身就是一种资源、网页还需要依赖很多其他的资源(图片、视频) &#xff08;1&#xff09;HTML 支持的资源主要包括以下几种类型&#xff1a; HTML 页…