算法入门-递归3

news2024/11/18 11:23:15

第四部分:递归

143.重排链表(中等)

题目:给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

img

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

示例 2:

img

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

第一种思路:

第一个比较容易想到的一个方法是利用线性表存储该链表,然后利用线性表可以下标访问的特点,直接按顺序访问指定元素,重建该链表即可,这里就不详细解释了。

class Solution {  
    public void reorderList(ListNode head) {  
        // 检查链表是否为空  
        if (head == null) {  
            return; // 如果为空,则直接返回  
        }  
        
        // 创建一个列表来保存链表中的节点  
        List<ListNode> list = new ArrayList<ListNode>();  
        ListNode node = head;  

        // 遍历链表并将每个节点添加到列表中  
        while (node != null) {  
            list.add(node); // 将当前节点添加到列表  
            node = node.next; // 移动到下一个节点  
        }  
        
        // 定义两个指针分别指向列表的开始和结束  
        int i = 0, j = list.size() - 1;  

        // 交替合并节点,直到两个指针相遇  
        while (i < j) {  
            list.get(i).next = list.get(j); // 将前半部分的节点指向后半部分的节点  
            i++; // 移动到前半部分的下一个节点  
            
            // 检查指针是否相遇  
            if (i == j) {  
                break; // 如果指针相遇,结束合并  
            }  
            
            list.get(j).next = list.get(i); // 将后半部分的节点指向前半部分的下一个节点  
            j--; // 移动到后半部分的上一个节点  
        }  
        
        // 在最后一个节点上设置 next 为 null,以终止链表  
        list.get(i).next = null; // 处理最后一个节点的链接  
    }  
}

第二种思路:

一开始的想法就是直接想像 《24.两两交换链表中的节点》一样套用递归的模板,写了一些代码后发现有点困难卡住了。看了解答知晓了递归也可以当作一个小步骤实现。

目标链表即为将原链表的左半端和反转后的右半端合并后的结果。

这样任务即可划分为三步:

  • 找到原链表的中点(参考「876. 链表的中间结点」)。

    • 我们可以使用快慢指针来 O(N) 地找到链表的中间节点。

  • 将原链表的右半端反转(参考「206. 反转链表」)。

    • 我们可以使用迭代法实现链表的反转。

  • 将原链表的两端合并。

    • 因为两链表长度相差不超过 1,因此直接合并即可。

  1. 找中间节点

    • 采用快慢指针法。在遍历链表时,慢指针每次移动一格,快指针每次移动两格。当快指针到达链表尾部时,慢指针正好在中间。

    • 通过这种方式找到中间节点后,可以将链表分为前半部分和后半部分。

  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 void reorderList(ListNode head) {  
        if (head == null || head.next == null) return; // 检查链表是否为空或只有一个节点  

        // 第一步:找到链表的中间节点  
        ListNode mid = findMiddle(head);   
        // 第二步:反转链表的后半部分  
        ListNode secondHalf = reverseList(mid.next);   
        mid.next = null; // 将链表分为两个部分  

        // 第三步:合并两个部分  
        mergeLists(head, secondHalf);  
    }  

    // 查找链表的中间节点  
    private ListNode findMiddle(ListNode head) {  
        ListNode slow = head; // 慢指针  
        ListNode fast = head; // 快指针  
        while (fast != null && fast.next != null) {  
            slow = slow.next; // 慢指针移动一步
            fast = fast.next.next; // 快指针移动两步  
        }  
        return slow; // 返回中间节点  
    }  

    // 反转给定的链表  
    private ListNode reverseList(ListNode head) {  
        ListNode prev = null; // 前一个节点  
        ListNode curr = head; // 当前节点  
        while (curr != null) {  
            ListNode nextTemp = curr.next; // 保存下一个节点  
            curr.next = prev; // 反转当前节点的指针  
            prev = curr; // 更新前一个节点为当前节点  
            curr = nextTemp; // 移动到下一个节点  
        }  
        return prev; // 返回反转后的链表头节点  
    }  

    // 合并两个链表  
    private void mergeLists(ListNode first, ListNode second) {  
        if (second == null) return; // 如果第二个链表为空,直接返回  

        ListNode temp1 = first.next; // 保存第一个链表的下一个节点  
        ListNode temp2 = second.next; // 保存第二个链表的下一个节点  

        first.next = second; // 将第二个链表的节点插入到第一个节点后  
        second.next = temp1; // 将第一个链表的下一个节点插入到第二个节点后  

        // 递归合并剩余的部分  
        mergeLists(temp1, temp2);   
    }  
}

206.反转链表(简单)

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

示例 1:

img

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

第一种思路:

  1. 基本情况

    • 检查链表是否为空(head == null)或只有一个节点(head.next == null)。如果是,直接返回 head,因为它已经是反转后的状态。

  2. 递归反转

    • 通过递归调用 reverseList(head.next),将当前节点的下一个节点开始的子链表反转。此时,newHead 将指向反转后的子链表的头节点。

  3. 反转连接

    • 在回溯的过程中,head.next.next = head; 将当前节点 head 连接到它的后继节点中,即将原先的下一个节点的指针指回到当前节点,完成反转。

    • head.next 设为 null,以切断链表,避免形成环。

  4. 返回新头节点

    • 最终返回 newHead,它是反转后的链表的新头节点。

/**  
 * 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);  
        
        // 将当前节点的下一个节点的指向改为当前节点  
        head.next.next = head; // 反转当前节点与其下一个节点的连接  
        head.next = null;      // 断开当前节点的下一个节点的引用,避免形成循环  

        // 返回反转后的新头节点  
        return newHead;  // newHead 是递归过程中最初的最后一个节点  
    }  
}

2.两数相加(中等)

题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

img

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

第一种思路:

整体思路是模拟加法过程,逐位计算并处理进位,最终获得两个数字的和,以链表形式返回。

  1. 输入表示:两个非负整数以链表形式表示,链表的每个节点存储一个数字,每个链表的头节点表示最低位。

  2. 初始化

    • 创建一个虚拟头节点,用于构建结果链表。

    • 定义一个指针 current 来指向结果链表的最后一个节点。

    • 初始化进位(carry)为0。

  3. 主循环

    • 使用 while 循环遍历两个链表,直到两个链表都遍历完且进位为0。

    • 在每次循环中,计算当前位的和(包括进位和当前节点的值)。

  4. 计算和进位

    • 如果链表 l1l2 不为空,将相应节点的值加到和(sum)中,并移动指针。

    • 更新进位为 sum / 10,当前位的下一个节点的值为 sum % 10

  5. 新节点处理

    • 创建新节点保存当前位的值,并将其链接到结果链表中。

  6. 返回结果

    • 循环结束后,返回虚拟头节点的下一个节点,得到完整的结果链表。

addTwoNumbers 方法:

  • dummyHead: 创建一个虚拟头节点,用于简化链表操作。

  • current: 当前节点指针,从 dummyHead 开始,逐步填充结果节点。

  • carry: 进位变量,用于存储两数相加时的进位。

  • 循环条件 while (l1 != null || l2 != null || carry != 0) 确保在处理完所有节点后仍然考虑进位。

  • 对于每个节点,分别累加两个链表的值和进位,创建新的节点并将其添加到结果链表中。

/**  
 * 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 addTwoNumbers(ListNode l1, ListNode l2) {  
        ListNode dummyHead = new ListNode(0); // 创建一个虚拟头节点,便于处理结果链表  
        ListNode current = dummyHead; // 当前节点指向虚拟头节点  
        int carry = 0; // 初始化进位为0  

        // 当l1或l2不为空,或者还有进位时,继续循环  
        while (l1 != null || l2 != null || carry != 0) {  
            int sum = carry; // 将当前的进位值加到sum中  

            // 如果l1不为空,将l1的值加到sum中  
            if (l1 != null) {  
                sum += l1.val; // 加上l1当前节点的值  
                l1 = l1.next; // 移动到l1的下一个节点  
            }  

            // 如果l2不为空,将l2的值加到sum中  
            if (l2 != null) {  
                sum += l2.val; // 加上l2当前节点的值  
                l2 = l2.next; // 移动到l2的下一个节点  
            }  

            carry = sum / 10; // 计算新的进位(sum的整除10)  
            current.next = new ListNode(sum % 10); // 创建新节点,值为sum的余数(当前位的值)  
            current = current.next; // 移动当前节点指针到新创建的节点  
        }  

        return dummyHead.next; // 返回结果链表,跳过虚拟头节点  
    }  
}  

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

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

相关文章

问题-小技巧-win11状态栏卡住

目前我只知道治标不治本的办法&#xff0c;打开任务管理器&#xff0c;找到Windows资源管理器右键重新启动&#xff0c;就可以解决这个问题。 这个问题我觉得是Win11自己的问题&#xff0c;等有新的发现&#xff0c;会进行补充。

使用Hutool工具类轻松生成验证码

效果图&#xff1a; 引入依赖&#xff1a; <!--hutool工具包--> <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.15</version> </dependency>核心代码 import cn.hutool.…

使用docker-compose运行kafka及验证(无需zookpeer)

前言&#xff1a;要求安装docker-compose kafka镜像版本&#xff1a;apache/kafka:3.8.0 可能存在镜像拉不下来的情况&#xff1a; 1、vim /etc/docker/daemon.json {"data-root":"/data/docker","registry-mirrors": ["https://docker.m…

高性价比百元蓝牙耳机如何选择?四款首选高性价比蓝牙耳机推荐

不知道什么时候开始&#xff0c;有线耳机悄悄的淡出了我们的视线。现在几乎都是蓝牙耳机首当前冲&#xff0c;因为比起有线耳机&#xff0c;蓝牙耳机更携带方便&#xff0c;拿出来就是秒连&#xff0c;体验感也不差。而且随着蓝牙耳机的价格不断下降&#xff0c;同时&#xff0…

【图文并茂】ant design pro 如何优雅奇妙地添加修改密码的功能

如上图所示&#xff0c;我们要加这样的一个功能&#xff0c;如何做呢&#xff1f; 首先要写好前端页面&#xff0c;再对接好后端&#xff0c;然后我们要判断当前密码是否正确&#xff0c;如果正确才能新密码修改好。 前端页面 src/pages/account/change-password/index.tsx …

【直观表格】常见神经网络计算复杂度对比 ——从时间复杂度和空间复杂度角度剖析

常见神经网络计算复杂度对比 ——从时间复杂度和空间复杂度角度剖析 【表格】常见神经网络计算复杂度对比 神经网络类型时间复杂度空间复杂度关键参数备注多层感知机&#xff08;MLP&#xff09; O ( n ⋅ d ⋅ h ) O(n \cdot d \cdot h) O(n⋅d⋅h) O ( d ⋅ h h ) O(d \c…

helm安装jenkins保姆级别

一、创建nfs服务器 这一步跳过、自行百度 注意&#xff1a;要给共享目录赋予权限chmod一下&#xff0c;不然到时候容器没办法在目录里面创建文件&#xff0c;初始化时候会报错误代码2 二、添加Jenkins的Helm仓库 helm repo add jenkinsci https://charts.jenkins.io helm re…

护眼台灯真的有用吗?学生护眼台灯十大牌子推荐

在当前近视患者剧增&#xff0c;我们发现近视还与青光眼的发生有关联&#xff0c;这是一种可能导致永久性视力丧失的眼病。青光眼通常是由于眼内压力过高造成的视神经损伤。高度近视患者的眼球结构变化可能增加眼压&#xff0c;从而提高患青光眼的风险。预防近视变得非常重要&a…

html js弹幕功能

效果如上 html <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><script charset"utf-8" src"https://unpkg.com/vue2.6.14/dist/vue.min.js" type"text/javascript">…

[C语言]-基础知识点梳理-编译、链接、预处理

前言 各位师傅大家好&#xff0c;我是qmx_07,今天来给大家讲解以下程序运行会经历哪些事情 翻译环境和运⾏环境 在ANSIC的任何⼀种实现中&#xff0c;存在两个不同的环境 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令&#xff08;⼆进制指令&a…

[FSCTF 2023]ez_php2

[FSCTF 2023]ez_php2 点开之后是一段php代码&#xff1a; <?php highlight_file(__file__); Class Rd{public $ending;public $cl;public $poc;public function __destruct(){echo "All matters have concluded";die($this->ending);}public function __call…

django宿舍管理系统 ---附源码98595

目 录 摘要 1 绪论 1.1 研究背景与意义 1.2 国内外研究现状 1.3论文结构与章节安排 2 宿舍管理系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据增加流程 2.2.2 数据修改流程 2.2.3 数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析…

vue vite创建项目步骤

1. 创建vue项目 node版本需18以上 不然报错 npm init vuelatest2. 项目配置 配置项目的icon配置项目的标题配置jsconfig.json 3. 项目目录结构划分 4.css样式的重置 npm install normalize.cssreset.css html {line-height: 1.2; }body, h1, h2, h3, h4, ul, li {padding…

aspose-words将tinymce中的换页符转换为word的换页符

aspose-words版本&#xff1a;21.1 java&#xff1a;1.8 tinymc&#xff1a;5.0.16 public void convertPageBreak() throws Exception{String sourceHtml "hello<!-- pagebreak -->world";sourceHtml sourceHtml.replaceAll("<!-- pagebreak -->…

鸿蒙崛起,前端/Java人才如何搭上这趟技术快车?

在科技飞速发展的今天&#xff0c;鸿蒙系统的崛起犹如一颗璀璨的新星&#xff0c;照亮了技术领域的新航道。对于前端和 Java 人才来说&#xff0c;这不仅仅是一个新的挑战&#xff0c;更是一次搭乘技术快车、实现职业飞跃的绝佳机遇。 一、鸿蒙崛起之势 鸿蒙系统自诞生以来&…

开放式耳机是什么意思?开放式对比入耳式耳机的音质更通透

开放式耳机是一种无需入耳的蓝牙耳机。它主要提供的是一种自然、开放的音频体验&#xff0c;并且无需封耳&#xff0c;能维持佩戴者对外界的感知和环境的联系。这种耳机并不需要深入耳道&#xff0c;但又能清晰听清耳机传来的内容&#xff0c;所以在佩戴方面会更加舒适。 开放…

告别本地硬件烦恼,一分钟教你用云端部署玩Stable Diffusion!

Stable diffusion有两种部署方式&#xff0c;分别是本地部署和云端部署。 本地部署需要把程序安装到自己的电脑上&#xff0c;因此对设备&#xff08;尤其是显卡显存&#xff09;要求比较高&#xff0c;但很多小伙伴反映自己设备不到位&#xff0c;升级设备费用成本过高&#…

【学习笔记】8、脉冲波形的变换与产生

本章简略记录。 8.1 单稳态触发器&#xff08;脉冲触发&#xff09; 单稳态触发器 应用于 &#xff1a;&#xff08;1&#xff09;脉冲整型&#xff08;2&#xff09;脉冲延时 &#xff08;3&#xff09;定时 单稳态触发器的工作特性&#xff1a; 没有触发脉冲作用时&#xf…

【EI会议征稿通知】 第四届航空航天、空气动力学与机电工程国际学术会议(AAME 2025)

第四届航空航天、空气动力学与机电工程国际学术会议&#xff08;AAME 2025&#xff09; 2025 4th International Conference on Aerospace, Aerodynamics and Mechatronics Engineering 会议将围绕“航天航空科学”、“空气动力学”、“机电工程”、“飞行器技术”等主题展开讨…

为什么制造企业智能化升级需要MES管理系统

在制造业的数字化转型浪潮中&#xff0c;MES管理系统的智能化升级扮演着至关重要的角色&#xff0c;它不仅重新定义了生产管理的边界&#xff0c;还为企业带来了前所未有的竞争力与可持续发展动力。本文将从数据赋能、人机深度融合、资源优化及生态协同四个维度&#xff0c;探讨…