算法入门-递归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; // 返回结果链表,跳过虚拟头节点  
    }  
}  
相关推荐
真的很上进1 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
呆呆的猫10 分钟前
【LeetCode】227、基本计算器 II
算法·leetcode·职场和发展
Tisfy12 分钟前
LeetCode 1705.吃苹果的最大数目:贪心(优先队列) - 清晰题解
算法·leetcode·优先队列·贪心·
余额不足1213832 分钟前
C语言基础十六:枚举、c语言中文件的读写操作
linux·c语言·算法
众拾达人33 分钟前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.35 分钟前
Mybatis-Plus
java·开发语言
不良人天码星35 分钟前
lombok插件不生效
java·开发语言·intellij-idea
守护者1701 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云1 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络1 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序