【算法系列篇】递归、搜索与回溯(一)

在这里插入图片描述

文章目录

  • 什么是递归、搜索与回溯算法
  • 1. 汉诺塔
    • 1.1 题目要求
    • 1.2 做题思路
    • 1.3 代码实现
  • 2. 合并两个有序链表
    • 2.1 题目要求
    • 2.2 做题思路
    • 2.3 代码实现
  • 3. 反转链表
    • 3.2 题目要求
    • 3.2 做题思路
    • 3.3 代码实现

什么是递归、搜索与回溯算法

递归算法是一种通过重复将问题分解为同类的子问题而解决问题的方法。递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。

搜索算法是利用计算机的高性能来有目的地穷举一个问题解空间的部分或所有的可能情况,从而求出问题的解的一种方法。主要包括枚举算法、深度优先搜索、广度优先搜索、A*算法、回溯算法、蒙特卡洛树搜索和散列函数等算法。

回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

那么,为什么会将这三个算法放在一起呢?其实搜索算法和回溯算法本质上都是递归算法,只是搜索决定了递归的方式,而回溯则是在递归搜索的时候,在函数返回值的时候将改变的量给还原回来。

在本系列算法中,我将为大家一步一步、一个阶段一个阶段的为大家分享涉及到相关知识的题目。

1. 汉诺塔

https://leetcode.cn/problems/hanota-lcci/

1.1 题目要求

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

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

你需要原地修改栈。

示例1:

输入:A = [2, 1, 0], B = [], C = []
输出:C = [2, 1, 0]

示例2:

输入:A = [1, 0], B = [], C = []
输出:C = [1, 0]

提示:

A中盘子的数目不大于14个。
class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {

    }
}

1.2 做题思路

这是⼀道递归⽅法的经典题⽬,我们可以先从最简单的情况考虑:

  • 假设 n = 1,只有⼀个盘⼦,很简单,直接把它从 A 中拿出来,移到 C 上;
  • 如果 n = 2 呢?这时候我们就要借助 B 了,因为⼩盘⼦必须时刻都在⼤盘⼦上⾯,共需要 3 步(为
    了⽅便叙述,记 A 中的盘⼦从上到下为 1 号,2 号):
    a. 1 号盘⼦放到 B 上;
    b. 2 号盘⼦放到 C 上;
    c. 1 号盘⼦放到 C 上。
    ⾄此,C 中的盘⼦从上到下为 1 号,2 号
  • 如果 n > 2 呢?这是我们需要⽤到 n = 2 时的策略,将 A 上⾯的两个盘⼦挪到 B 上,再将最⼤的盘
    ⼦挪到 C 上,最后将 B 上的⼩盘⼦挪到 C 上就完成了所有步骤。例如 n = 3 时如下图
    在这里插入图片描述

然后再假设我们要将 4 个圆盘从 A 柱上借助 B 柱给移动到 C 柱上。
在这里插入图片描述

先将上面的三个盘子从 A 柱借助 C 柱移动到 B 柱。
在这里插入图片描述

在这里插入图片描述

然后将 A 柱剩下的那个最大的盘子移动到 C 柱之后,再将 B 柱上的 3 个盘子通过 A 柱移动到 C 柱上。

在这里插入图片描述

汉诺塔的规则是一次只能移动一个盘子并且需要保证较小的盘子在较大盘子的上面。那么要想将这 4 个盘子从 A 柱上借助 B 柱移动到 C 柱的话,首先就需要想办法将 A 柱上的 3 个盘子给移动到 B 柱上,然后将剩下的最大的盘子移动到 C 柱上,然后再将 B 柱上的 3 个盘子借助 A 柱移动到 C 柱上,最终就能达到我们的目标。而将 3 个盘子从 A 柱借助 B 柱移动到 C 柱就可以借助最上面的那个例子实现。

我们只看上面三个盘子的起点和终点就可以发现这三个盘子是从 A 柱借助 B 柱移动到 C 柱上,跟上面 3 个盘子的情况是一样的。当盘子为 5 个的时候,也是先将上面的 4 个盘子借助 C 柱移动到 B 柱,然后将剩下的一个盘子移动到 C 柱,最后再将 B 柱上的 4 个盘子借助 A 柱给移动到 C 柱上。上面 4 个盘子的移动路径也是
A柱 -> C柱,符合将大事化小的思想,所以我们就可以将汉诺塔的问题看成是一个递归。

并且通过上面的例子,我们可以总结出:当 A 柱只有一个盘子的时候,可以直接将这个盘子从 A 柱移动到 C 柱上,其他情况则遵循下面的步骤。

  1. 将 n-1 个盘子从 A 柱借助 C 柱移动到 B 柱。
  2. 将 A 柱上剩余的 1 个盘子直接移动到 C 柱
  3. 将 B 柱上的 n-1 个盘子借助 A 柱移动到 C 柱

那么知道了思路是什么,那么代码该怎么写呢?递归代码该如何写才不会出现栈溢出/死递归的问题呢?

其实我们要有这样的一种思想:以宏观的角度来解决微观问题,这是什么意思呢?就是当我想要将 n-1 个盘子从 A 柱借助 C 柱移动到 B 柱的时候,我们只需要将这件事交给函数,并且相信这个函数一定能够帮助我们解决这个问题。

1.3 代码实现

class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        dfs(A, B, C, A.size()); //将n个盘子从A柱借助B柱移动到C柱
    }

    private void dfs(List<Integer> A, List<Integer> B, List<Integer> C, int n) {
        //当A柱上只有一个盘子的时候,则只需要将这个盘子移动到C柱上
        if (n == 1) {
            C.add(A.remove(A.size() - 1));
            return;
        }
        dfs(A, C, B, n - 1); //将n-1个盘子从A柱上借助C柱移动到B柱
        C.add(A.remove(A.size() - 1)); //将A柱剩下的那个盘子移动到C柱
        dfs(B, A, C, n - 1); //将B柱上的n-1个盘子借助A柱移动到C柱
    }
}

在这里插入图片描述

如果大家对这个代码有疑问的话,大家可以详细的画出这个代码的递归函数过程。

其实想要做好递归的题目,关键就在于我们能够总结出规律,只要发现这个提供通过一个函数就可以解决,那么基本就可以做出这个决定:使用递归来解决,并且在做递归类的题目的时候,我建议大家不要去细究它是如何递归的,我们只需要相信这个函数一定能帮助我们解决问题就可以了。

2. 合并两个有序链表

https://leetcode.cn/problems/merge-two-sorted-lists/

2.1 题目要求

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

示例 1:

在这里插入图片描述

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        
    }
}

2.2 做题思路

这道题目,相比大家之前肯定做过了吧,之前的做法就是用两个指针分别遍历这两个链表,一开始两个指针 cur1 和 cur2 都指向链表的头结点,如果 cur1 指向的节点的值小于 cur2 指针指向的节点的值,那么就将 cur1 节点的下一个节点指向 cur2 ,然后 cur2 向后移动一位,一次操作就能达到合并两个有序链表的操作了。

那么为什么在写递归算法的时候,还会将这个题目给搬出来呢?这是因为这道题目同样可以使用递归的方式来解决,为什么这么说呢?合并链表的操作也是跟上面的思路相同,比较两个指针指向的节点的大小,然后根据这两个节点的大小来讲这两个节点进行排序,也就是说两个节点排序可以使用同一个函数来实现,并且合并两个整个链表,实际上也是这两个链表中的两个节点在比较。这就适合递归将大事化小,可以使用同一个函数解决的思路。

那么,如何使用递归解决这个问题呢?在一个函数中我们只解决两个节点的比较,如果 cur1 指针指向的节点的值小于 cur2 指向的节点的值的话,那就需要 cur2 指向的节点与 cur1.next 之后的节点进行比较,并且将比较的结果给接到 cur1 节点的后面。

在这里插入图片描述
在这里插入图片描述

2.3 代码实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    	//当l1链表或者l2链表为空的时候可以直接返回
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        }else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

在这里插入图片描述

3. 反转链表

https://leetcode.cn/problems/reverse-linked-list/

3.2 题目要求

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

示例 1:
在这里插入图片描述

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:
在这里插入图片描述

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        
    }
}

3.2 做题思路

反转整个链表其实还是两两节点进行反转,并且反转的过程是一样的,所以就可以使用我们的递归来解决这个问题。

看到这个题目我们可以想到的方法就是从链表的第一个节点开始,将该节点的指向指向前一个节点,那么就可以将这个链表分为两个部分,一个就是第一个节点,另一部分就是后n-1个节点,我们先将后面n-1个节点进行反转,然后将反转的链表的头结点返回,最后在将这个第一个节点接在反转后的链表的尾部,就得到了最终的反转链表。

在这里插入图片描述
在这里插入图片描述

3.3 代码实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode newHead = reverseList(head.next); //先反转后面n-1个节点
        //将head节点加在后面n-1个节点反转后的链表的后面
        head.next.next = head;
        head.next = null;

        return newHead;
    }
}

在这里插入图片描述

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

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

相关文章

是宝箱还是潘朵拉魔盒?ChatGPT正在悄然改变世界...

2022年11月30日&#xff0c;OpenAI推出了一款全新的对话式通用人工智能工具——ChatGPT。与以往的人工智障不同&#xff0c;ChatGPT能够与用户进行自然、流畅的对话&#xff0c;帮助人们解答各种问题&#xff0c;提供所需的帮助和支持。 据报道&#xff0c;ChatGPT在推出短短几…

技术阅读周刊第第8️⃣期

技术阅读周刊&#xff0c;每周更新。 历史更新 20231103&#xff1a;第四期20231107&#xff1a;第五期20231117&#xff1a;第六期20231124&#xff1a;第七期 Prometheus vs. VictoriaMetrics (VM) | Last9 URL: https://last9.io/blog/prometheus-vs-victoriametrics/?refd…

中碳CCNG:碳交易领域的革命者

在全球碳减排努力日益增强的背景下&#xff0c;中国碳中和发展集团有限公司&#xff08;简称中碳CCNG&#xff09;正以其创新的碳交易平台引领行业新趋势。中碳CCNG提供的一站式综合服务不仅包括碳信用的托管、买卖和抵消&#xff0c;而且通过其综合性数字平台&#xff0c;促进…

Mysql- 流程函数-(If, CASE WHEN)的使用及练习

目录 4.1 If函数语法格式 4.2 CASE WHEN 条件表达式格式 4.3 练习题1 4.4 练习题2 4.5 练习题3-行转列 4.6 牛客练习题 4.7 LeetCode练习题 4.1 If函数语法格式 IF(expr1,expr2,expr3) 解释&#xff1a; 如果表达式expr1true(expr1 <> 0 and expr1 <> NUL…

element-plus组件中的el-drawer的使用

在项目的制作过程中经常会用到弹窗组件&#xff0c;这里假设一种情况当你在一个页面需要多个弹窗组件的时候怎么样才能精准的打开和关闭对应的弹窗呐&#xff1f;&#xff1f; ① 绑定一个点击事件----【给点击事件传入一个下标】这里是打开事件 ② 使用element-plus中的 :befo…

Redis系列之keys命令和scan命令性能对比

项目场景 Redis的keys *命令在生产环境是慎用的&#xff0c;特别是一些并发量很大的项目&#xff0c;原因是Redis是单线程的&#xff0c;keys *会引发Redis锁&#xff0c;占用reids CPU&#xff0c;如果key数量很大而且并发是比较大的情况&#xff0c;效率是很慢的&#xff0c…

ActiveMQ 反序列化漏洞(CVE-2015-5254)

ActiveMQ 反序列化漏洞 Apache ActiveMQ是一种开源的消息代理&#xff08;message broker&#xff09;&#xff0c;被广泛用于应用程序之间的消息传递。它提供可靠的消息传递模式&#xff0c;如发布/订阅、点对点和请求/响应&#xff0c;非常适合构建分布式系统和应用程序集成…

感觉到自己思想扭曲了

突然觉得自己思想有点扭曲。 ​起因是近期备婚&#xff0c;需要给男方家人买衣服。问男朋友妹妹衣服预算多少&#xff0c;说是500内&#xff0c;然后想想自己这个新娘子&#xff0c;那一身衣服绞尽脑汁凑满减不到300。再联想到装饰新房&#xff0c;新房买家具&#xff0c;为了省…

为 3D 模型制作纹理的 9 种最佳方法

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 与普遍的看法相反&#xff0c;3D 模型的纹理创建更加简单。虽然对细节…

【PyQt学习篇 · ⑫】:QVBoxLayout和QHBoxLayout布局管理器的使用

文章目录 QVBoxLayout和QHBoxLayout的介绍.addStretch()的使用方法.setSpacing()方法的使用.setAlignment()的使用.setFixedSize()的使用QMainWindow中使用布局管理器 QVBoxLayout和QHBoxLayout的介绍 QVBoxLayout 和 QHBoxLayout 是 PyQt 中用于实现垂直和水平布局的两个布局…

CoreDNS实战(五)-接入prometheus监控

1 背景 Prometheus插件作为coredns的Plugins&#xff0c;默认情况下是内置在coredns中&#xff0c;如果是自己编译安装的版本&#xff0c;需要注意在编译安装的时候的plugin.cfg文件中添加了prometheus:metrics&#xff0c;这样才能确保编译成功。 # 首先我们检查一下运行的版…

Python-炸弹人【附完整源码】

炸弹人 炸弹人是童年的一款经典电子游戏&#xff0c;玩家控制一个类似"炸弹人"的角色&#xff0c;这个角色可以放置炸弹&#xff0c;并在指定的时间内引爆它们消灭敌人以达到目标&#xff0c;此游戏共设有两节关卡&#xff0c;代码如下&#xff1a; 运行效果&#x…

介绍几个有意思的 GitHub 仓库

大家好&#xff0c;我是风筝。 今天介绍几个很有意思的 github 开源项目&#xff0c;看过之后就会发现&#xff0c;github 果然深意暗藏。 GitHub对于程序员来说&#xff0c;再熟悉不过了&#xff0c;绝大多数时候&#xff0c;我们到上面都是为了学习高质量的源代码&#xff…

12 月 10 日,融云在 Google DevFest 上海站等你!

Welcome to DevFest!RongCloud2023 Google DevFest 上海站关注【融云全球互联网通信云】了解更多 时间&#xff1a;2023 年 12 月 10 日&#xff08;周日&#xff09;地点&#xff1a;上海市浦东新区新金桥路 1599 号&#xff0c;东方万国宴会中心 (下沉式广场)主讲&#xff1a…

Unity3D对CSV文件操作(创建、读取、写入、修改)

系列文章目录 Unity工具 文章目录 系列文章目录前言一、Csv是什么&#xff1f;二、创建csv文件2-1、构建表数据2-2、创建表方法2-3、完整的脚本&#xff08;第一种方式&#xff09;2-4、运行结果2-5、完整的脚本&#xff08;第二种方式&#xff09;2-6、运行结果2-7、想用哪种…

【性能测试】业务/吞吐量与存量数据设计关系+压测常见解决方案

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、性能测试中业务…

设计模式之GoF23介绍

深入探讨设计模式&#xff1a;构建可维护、可扩展的软件架构 一、设计模式的背景1.1 什么是设计模式1.2 设计模式的历史 二、设计模式的分类2.1 创建型模式2.2 结构型模式2.3 行为型模式 三、七大设计原则四、设计模式关系结论 :rocket: :rocket: :rocket: 在软件开发领域&…

软件工程之系统质量

从公众号转载&#xff0c;关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一 、质量标准化 1.什么是质量标准化 通过标准化各条业务线的研发流程&#xff0c;以做的比较好的业务线作为标准样板间&#xff0c;规范出一套标…

使用squid配置高匿代理

背景介绍 为什么要设置高匿代理&#xff1f; 在家和开放平台交互的时候&#xff0c;需要设置白名单&#xff0c;否则无法交互。家里的白名单一直变。 服务部署到服务器太麻烦&#xff0c;调试不方便。 于是就想通过代理的方式&#xff0c;让服务器替我发送这次请求&#xf…

使用Java语言进行账户登录和密码输入

一、操作原理 使用Scanner扫描器进行扫描&#xff0c;使用if语句、if-else语句和else进行账户和密码的验证。 二、相关代码 import java.util.Scanner; public class CheckLoginDemo {public static void main(String[] args){try (Scanner scan new Scanner(System.in)) …