92. 反转链表 II(数据结构系列)

该博客聚焦于反转从位置 m 到 n 的链表问题,要求一趟扫描完成。先介绍反转整个链表思路,再阐述反转从 head 节点开始的 n 段链表,最后解决反转链表一部分的问题,通过递归调用将问题转化,还给出了相应代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 题目

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
在这里插入图片描述

2. 解题思路

2.1. 反转整个链表

首先我们要知道最基本的反转链表思路:

ListNode reverse(ListNode head) {
    if (head.next == null) return head;
    ListNode last = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return last;
}

2.2. 反转从head节点开始的n段链表

这次我们实现一个这样的函数–>反转从head开始的n个节点:

ListNode successor = null; // 后驱节点

// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode reverseN(ListNode head, int n) {
    if (n == 1) { 
        // 记录第 n + 1 个节点
        successor = head.next;
        return head;
    }
    // 以 head.next 为起点,需要反转前 n - 1 个节点
    ListNode last = reverseN(head.next, n - 1);

    head.next.next = head;
    // 让反转之后的 head 节点和后面的节点连起来
    head.next = successor;
    return last;
}    

比如说对于下图链表,执行 reverseN(head, 3)
image.png
image.png

[!question] 为什么不是 head = reverseBetween(…),而是 head.next = reverseBetween(…)?
这个问题的关键点是:我们要反转的是从第 m 个节点到第 n 个节点的子链表,而不是整个链表的 head。我们只需要修改 head.next,而不是 head 本身。
reverseBetween() 理解为反转结束后的head节点就行。

2.3. 反转链表的一部分

现在解决我们最开始提出的问题,给一个索引区间 [m,n](索引从 1 开始),仅仅反转区间中的链表元素。

ListNode reverseBetween(ListNode head, int m, int n)

首先,如果 m == 1,就相当于反转链表开头的 n 个元素嘛,也就是我们刚才实现的功能reverseN()。通过递归调用,把问题变成“从下一个节点开始的链表中,反转第 m-1n-1 个节点”。每一次递归都“前进一位”,直到 m == 1 时触发 base case,进入实际反转。
假设链表是:1 → 2 → 3 → 4 → 5,现在要执行 reverseBetween(head, 2, 4),反转第2到第4个节点。
第一次调用:

  • m = 2, n = 4, head = 1
  • 不满足 m=1,于是执行:head.next = reverseBetween(head.next, 1, 3);

第二次调用:

  • m = 1, n = 3, head = 2
  • 进入 base case,调用 reverseN(head, 3),会反转 2 → 3 → 4 成为 4 → 3 → 2

此时返回的是新的头结点 4,所以:head.next = 4(即反转后新的头结点),而此时 head 是 1,那么1.next = 4;这样就把 1 → 2 → 3 → 4 → 5 改成了:1 → 4 → 3 → 2 → 5

这部分的完整代码:

ListNode reverseBetween(ListNode head, int m, int n) {
    // base case
    if (m == 1) {
        return reverseN(head, n);
    }
    // 前进到反转的起点触发 base case
    head.next = reverseBetween(head.next, m - 1, n - 1);
    return head;
}

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 {
    ListNode successor = null;//后驱节点

    /*
    反转链表中的前n个节点
    */
    public ListNode reverseN(ListNode head, int n) {
        if (n == 1) {//经过下面的递归,一定会来到这里,把后驱节点赋上值。
            successor = head.next;//记录反转后的头节点 后面原来连接的那个节点
            return head;//反转一个节点,就是它自己
        }
        //以head.next为起点,需要反转前n-1个节点
        ListNode last = reverseN(head.next, n - 1);

        head.next.next = head;
        head.next = successor;
        return last;
    }


    public ListNode reverseBetween(ListNode head, int m, int n) {

        if (head == null || head.next == null) {
            return head;
        }

        //base case  经过递归最终会来到这里的。
        if (m == 1) {//left=1,相当于链表开头的right个元素
            return reverseN(head, n);
        }

        /*对于head.next来说就是反转区间[left-1,right-1]
        reverseBetween();会返回反转后的链表头节点,head.next就是把反转后的头节点和head连接起来

         前进到反转的起点触发 base case
         */
        head.next = reverseBetween(head.next, m - 1, n - 1);
        return head;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值