算法入门-递归3

第四部分:递归

143.重排链表(中等)

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

复制代码
L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

复制代码
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

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

示例 1:

复制代码
输入:head = [1,2,3,4]
输出:[1,4,2,3]

示例 2:

复制代码
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

第一种思路:

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

java 复制代码
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. 交替合并链表

    • 将前半部分和反转后的后半部分交替合并。具体操作是,从前半部分取一个节点,然后从后半部分取一个节点,重复这个过程,直到合并完所有节点。

    • 这个过程可以使用递归来实现,每次先取出一个节点,然后递归合并剩下的部分。

java 复制代码
/**  
 * 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:

复制代码
输入: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,它是反转后的链表的新头节点。
java 复制代码
/**  
 * 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:

复制代码
输入: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) 确保在处理完所有节点后仍然考虑进位。

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

java 复制代码
/**  
 * 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; // 返回结果链表,跳过虚拟头节点  
    }  
}  
相关推荐
卡尔特斯4 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源4 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole4 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫4 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide5 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261355 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源5 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
CoovallyAIHub6 小时前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
Java中文社群6 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心6 小时前
从零开始学Flink:数据源
java·大数据·后端·flink