搜索回溯算法(DFS)1------递归

news2024/9/22 4:13:32

目录

简介:

递归问题解题的思路模板

例题1:汉诺塔

例题2:合并两个有序链表

例题3:反转链表

例题4:两两交换链表中的节点

例题5:Pow(x,n)-快速幂

结语:


简介:

本系列将会带大家深入理解搜索中的一大分支深搜,深搜是离不开递归的和回溯思想的(优化需要剪枝),故我会在例题中详细指出解决这一系列问题的思考思路和解题技巧。

那么我们就从递归开始(深搜的基础)也就是本文中主要介绍的。

什么是递归?

简单来说就是函数自己调用自己。

为什么会用到递归?

大问题可以拆解成相同的子问题,且子问题的解法和大问题的一模一样,这是就可以用到递归。

在解决⼀个规模为n的问题时,如果满⾜以下条件,我们可以使用递归来解决:

a. 问题可以被划分为规模更⼩的⼦问题,并且这些⼦问题具有与原问题相同的解决⽅法。

b. 当我们知道规模更⼩的⼦问题(规模为n-1)的解时,我们可以直接计算出规模为n的问题的解。

c. 存在⼀种简单情况,或者说当问题的规模⾜够⼩时,我们可以直接求解问题。

⼀般的递归求解过程如下:

a. 验证是否满⾜简单情况。

b. 假设较⼩规模的问题已经解决,解决当前问题。

上述步骤可以通过数学归纳法来证明。

如何理解递归?

不要太在意细节,相信函数。 

递归问题解题的思路模板

当然在设计递归函数之前最重要的是你要你的递归函数干嘛。

1.递归函数的作用

2.相同子问题------------函数头

3.只关心某一个子问题是如何解决的------------函数体

4.递归出口

建议友友在写递归类型的题目是一定要把这三个地方考虑清楚了再下手。

最后就是相信函数。

例题1:汉诺塔

链接:汉诺塔

题目简介:

递归问题非常经典的一道题目,在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。

解法:

我们可以看到每次都可以把大问题分解成相同的子问题,且子问题的解决方法和大问题的一模一样故我们可以使用递归来处理。 

1.递归函数的作用

函数作⽤:将A中的上⾯n个盘⼦挪到C中。这里的A和C是函数参数的A和C不是实际的(很重要)。

2.相同子问题(函数头)

我们要设计一个函数头来完成汉诺塔的递归过程,我们可以看到我们需要三根柱子和要记录下来还剩下几个盘子。故我们的函数头可以设计成public void dfs(List<Integer> a, List<Integer> b, List<Integer> c, int n)。关于List<Integer>如果友友还没有学到的话可以把他看成一个数组。

3.只关心某一个子问题是如何解决的(函数体)

我们取第三层汉诺塔来研究(大问题被拆成3层汉诺塔)。

我们发现子问题刚来三件事

1.把A上面n - 1 个盘子通过C移动到B 

2.把A上面最后一个盘中移动到C

3.把B上面n - 1个盘中通过A 移动到C

dfs(a, c, b, n - 1);

c.add(a.remove(a.size() - 1));//这里是因为给出的例题这样做就是把盘中从A移动到C(理解思路即可)

dfs(b, a, c, n - 1);

4.递归出口

当问题的规模变为n=1时,即只有⼀个盘⼦时,我们可以直接将其从A柱移动到C柱。

本例题代码实现如下:

class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        dfs(A,B,C,A.size());
    }
    public void dfs(List<Integer> A, List<Integer> B, List<Integer> C,int n ){
        if(n == 1){
            C.add(A.remove(A.size() - 1));
            return;
        }
        dfs(A,C,B,n - 1);
        C.add(A.remove(A.size() - 1));
        dfs(B,A,C,n - 1);
    }
}

关于本例题要注意的点:c.add(a.remove(a.size() - 1));可能有的友友会纠结为什么不是remove(0)呢,其实自己模拟一下即可,我们移走盘子是从最上面那个盘子开始移动的。

不用太深究函数的细节(陷入将无法自拔),如果是第一次的话可以去b站上看看递归的全过程细节(这里的不用深究是建立在已经对它的展开有一定理解了,第一次学汉诺塔的话我还是建议大家可以看看别人推的整个过程,理解更加深刻)。

例题2:合并两个有序链表

链接:合并两个有序链表

题目简介:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

解法:

通过分析题目我们发现可以把大问题拆成下图的子问题,上面的1已经合并有序现在要让2,4和下面链表合并有序。

故我们可以用递归来解决这道问题。

1.递归函数的作用

交给你两个链表的头结点,你帮我把它们合并起来,并且返回合并后的头结点.

2.相同子问题(函数头)

由上图可以看到函数要包含两个链表还要能返回合并后的链表故函数头设定为:public ListNode mergeTwoLists(ListNode l1, ListNode l2)

3.只关心某一个子问题是如何解决的(函数体)

选择两个头结点中较小的结点作为最终合并后的头结点,然后将剩下的链表交给递归函数去处理。

4.递归出口

当某⼀个链表为空的时候,返回另外⼀个链表。

代码如下:

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        if(list1.val <= list2.val){
            list1.next = mergeTwoLists(list1.next,list2);
            return list1;
        }else{
            list2.next = mergeTwoLists(list1,list2.next);
            return list2;
        }

    }
}

例题3:反转链表

链接:反转链表

题目简介:

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

解法:

要想让1到5逆序,可以让5逆序后让1到4逆序一直这样下去我们不难发现这就是递归。

1.递归函数的作用

交给你⼀个链表的头指针,你帮我逆序之后,返回逆序后的头结点。

2.相同子问题(函数头)

需要传入链表,5节点逆序后要把逆序后的新头节点返回故将函数体设为public ListNode reverseList(ListNode head)

3.只关心某一个子问题是如何解决的(函数体)

先把当前结点之后的链表逆序,逆序完之后,把当前结点添加到逆序后的链表后⾯即可。

4.递归出口

当前结点为空或者当前只有⼀个结点的时候,不⽤逆序,直接返回。

代码如下:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode newHead = head;
        if(head == null){
            return head;
        }
        ListNode cur = head.next;
        ListNode prev = head;
        head.next = null;
        while(cur != null){
            ListNode next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
}

例题4:两两交换链表中的节点

链接:两两交换链表中的节点

题目简介:

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

 解法:

我们通过阅读题目可以发现要使整个链表两两交换 ,可以将大问题分为把1和2后面的节点两两交换,把一二交换即可,同理3和4也是同样的处理思路。

1.递归函数的作用

交给你⼀个链表,将这个链表两两交换⼀下,然后返回交换后的头结点;

2.相同子问题(函数头)

需要传入原始链表和返回新的头节点故设计为:public ListNode swapPairs(ListNode head)

3.只关心某一个子问题是如何解决的(函数体)

先去处理⼀下第⼆个结点往后的链表,然后再把当前的两个结点交换⼀下,连接上后面处理后的链表;

4.递归出口

当前结点为空或者当前只有⼀个结点的时候,不⽤交换,直接返回。

代码如下:

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode newHead = head.next;
        head.next = swapPairs(head.next.next);
        newHead.next = head;
        return newHead;
    }
}

例题5:Pow(x,n)-快速幂

链接:Pow(x,n)

题目简介:

实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。

  解法:

这题不能使用1个1个数的分离递归(会超时),我们这里要采用二分的思路,具体实现如下:

1.递归函数的作用

求出x 的n 次⽅是多少,然后返回;

2.相同子问题(函数头)

需要传入需要n次方的x和n还要将其返回public double pow(double x, int n)

3.只关心某一个子问题是如何解决的(函数体)

先求出x 的n / 2 次⽅是多少,然后根据n 的奇偶,得出x 的n 次⽅是多少;

4.递归出口

当n 为0 的时候,返回1 即可。

代码如下:

最上面要区分一下正负数的区别即可。

class Solution {
    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;
    }
}

总结:本文章是搜索回溯的第一篇,带大家再复习了一下递归,后续的章节会带领大家深度理解深搜和回溯算法。

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

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

相关文章

攻防世界-get_post

题目信息 相关知识 -G&#xff1a;表示GET请求&#xff0c;缺省POST -d参数用于发送 POST 请求的数据体 使用-d参数以后&#xff0c;HTTP 请求会自动加上标头Content-Type : application/x-www-form-urlencoded。并且会自动将请求转为 POST 方法&#xff0c;因此可以省略-X PO…

Vue.js 深度解析:模板编译原理与过程

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Springboot+vue的船舶监造系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的船舶监造系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的船舶监造系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff…

山西电力市场日前价格预测【2024-02-25】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-02-25&#xff09;山西电力市场全天平均日前电价为386.45元/MWh。其中&#xff0c;最高日前电价为765.92元/MWh&#xff0c;预计出现在18:45。最低日前电价为203.79元/MWh&#xff0c;预计…

【计算机毕业设计】044学生管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

C# 不可识别数据库格式问题

C#是一种流行的编程语言&#xff0c;用于开发各种类型的应用程序&#xff0c;包括与数据库交互的应用程序。然而&#xff0c;在处理数据库时&#xff0c;有时会遇到一些错误和问题。其中之一就是数据库格式不可识别的错误。 在C#中&#xff0c;我们通常使用ADO.NET来连接和操作…

【教程】Kotlin语言学习笔记(四)——方法(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 文章目录 【…

openGauss学习笔记-235 openGauss性能调优-系统调优-资源负载管理-资源管理准备-创建资源池

文章目录 openGauss学习笔记-235 openGauss性能调优-系统调优-资源负载管理-资源管理准备-创建资源池235.1 背景信息235.2 前提条件235.3 操作过程235.3.1 创建资源池235.3.2 管理资源池235.3.3 删除资源池 235.4 查看资源池的信息 openGauss学习笔记-235 openGauss性能调优-系…

探索Ubuntu命令行:常见问题与解决方案

一、引言 Ubuntu&#xff0c;作为一款流行的Linux发行版&#xff0c;其命令行界面&#xff08;CLI&#xff09;为用户提供了丰富的功能和灵活性。然而&#xff0c;对于新手来说&#xff0c;命令行可能会带来一些挑战。本文将探讨一些在使用Ubuntu命令行时可能遇到的问题及其解决…

Python算法100例-3.1 回文数

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.问题拓展7.巧用字符串技巧 1&#xff0e;问题描述 打印所有不超过n&#xff08;取n<256&#xff09;的其平方具有对称性质的数&#xff08;也称回…

加密与安全_ 凯撒密码

文章目录 Pre概述Code 实现 凯撒密码字母频率分析攻击Code解密凯撒密码 小结 Pre PKI - 02 对称与非对称密钥算法 概述 凯撒密码是一种简单的替换加密技术&#xff0c;也称为移位密码。它是古典密码学中最早的密码之一&#xff0c;得名于古罗马军队领袖凯撒尤利乌斯&#xff…

VMware虚拟机安装Linux

1.新建虚拟机 2. 安装操作系统 等待 选择中文 点软件选择 选择下面的GNOME桌面 禁用KDUMP 点进安装位置&#xff0c;点完成就可以了 网络连接&#xff0c;右上角打开 开始安装&#xff0c;输入ROOT密码&#xff0c;创建用户 点击重启&#xff0c;等待 重启完成之后出现下面的界…

MATLAB环境下基于离散小波变换的心电信号伪影去除及PQRST波检测

可穿戴个人健康监护系统被广泛认为是下一代健康监护技术的核心解决方案。监护设备不断地感知、获取、分析和存储大量人体在日常活动中的生理数据&#xff0c;为人体的健康状况提供必要的、准确的、集成的和长期的评估和反馈。在心电监测领域&#xff0c;可穿戴传感器具有以下应…

镜头畸变模型及去畸变的原理

1. OpenCV去畸变undistortPoints原理解析 Opencv中镜头畸变包含了径向畸变和切向畸变&#xff0c;本章节主要阐述镜头畸变模型以及去畸变的原理。 1.1 镜头畸变模型 参考opencv文档 https://docs.opencv.org/3.1.0/d4/d94/tutorial_camera_calibration.html&#xff0c;opencv…

布隆过滤器实战

一、背景 本篇文章以解决实际需求的问题的角度进行切入&#xff0c;探讨了如果使用布隆过滤器快速丢弃无效请求&#xff0c;降低了系统的负载以及不必要的流量。 我们都知道布隆过滤器是以占用内存小&#xff0c;同时也能够实现快速的过滤从而满足我们的需求&#xff0c;本篇…

⭐每天一道leetcode:27.移除元素(简单;vector)

⭐今日份题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中…

更新至2023年,上市公司ESG数据合集(四份数据:盟浪、华证、商道融绿、富时罗素)

更新至2023年&#xff0c;上市公司ESG数据合集&#xff08;四份数据&#xff1a;盟浪、华证、商道融绿、富时罗素&#xff09; 1、时间&#xff1a; 富时罗素ESG评分&#xff1a;2018-2023年 华证ESG评级&#xff1a;2009-2023年 盟浪ESG评级&#xff1a;2014-2023 商道融…

【Linux】输入系统应用

# 前置知识 (1)输入子系统分为三层&#xff0c;分别是事件处理层、核心层、设备驱动层&#xff1b; (2)鼠标移动、键盘按键按下等输入事件都需要通过设备驱动层→核心层→事件处理层→用户空间&#xff0c;层层上报&#xff0c;直到应用程序; 事件处理层 (1)事情处理层主要是负…

数码管的动态显示(一)

1.原理 把每一个数码管闪烁的时间设置为1ms&#xff0c;肉眼观察不到就会认为6个数码管在同时闪烁。 实验目标&#xff1a; 使用6位8段数码管实现数码管的动态显示&#xff0c;显示的内容就是0-999_999。当计数到最大值&#xff0c;让他归零&#xff0c;然后循环显示。每0.1秒…

【嵌入式移植】8、U-Boot源码分析5—启动过程分析start.S

U-Boot源码分析5—启动过程分析start.S 1、boot0.h2、reset2.1、vectors2.2、ELn2.2.1 EL32.2.2、EL2、EL1 2.3、SMPEN2.3、core errate2.4、lowlevel_init 前面从U-Boot编译的角度分析了其Makefile、链接脚本等&#xff0c;本章开始正式分析U-Boot启动过程 从上一篇文章7、U-…