力扣面试150(50/150)

8.8 92. 反转链表 II

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

我的思路:找到左右的指针,切割出一段链表,反转之后又插回去。

看起来挺简单的,其实难得我不想说话。

1、创建一个虚拟头指针->解决只有一个数据的问题

复制代码
    const dummy = new ListNode(0);
    dummy.next = head;

2、定位到切割链表的前一个节点font

复制代码
    let font = dummy;
    for (let i = 0; i < left - 1; i++) {
        font = font.next;
    }

3、定位待反转区间的头节点 leftP 和尾节点 rightP

复制代码
    let leftP = font.next;
    let rightP = leftP;
    for (let i = 0; i < right - left; i++) { // 从 leftP 开始走 right-left 步
        rightP = rightP.next;
    }

4、切断链表

复制代码
    const back = rightP.next;
    rightP.next = null; // 将子链表尾部切断,方便反转

5、进行翻转:

复制代码
const reserveList = function(head){
    let pre = null;
    let curr = head;
    while(curr){
        const next = curr.next;
        curr.next = pre;
        pre = curr;
        curr = next;
    }
    return pre;
}
 const reversedHead = reserveList(leftP);

6、拼接回去

复制代码
    font.next = reversedHead;      // 前驱节点 -> 新头
    leftP.next = back;             // 新尾(原头) -> 后续节点

整体代码:

复制代码
var reverseBetween = function(head, left, right) {
    // 1. 创建虚拟头节点,并连接到原链表头:解决出现left=1的情况
    const dummy = new ListNode(0);
    dummy.next = head;

    // 2. 定位到待反转区间的前一个节点 font
    let font = dummy;
    for (let i = 0; i < left - 1; i++) {
        font = font.next;
    }

    // 3. 定位待反转区间的头节点 leftP 和尾节点 rightP
    let leftP = font.next;
    let rightP = leftP;
    for (let i = 0; i < right - left; i++) { // 从 leftP 开始走 right-left 步
        rightP = rightP.next;
    }

    // 4. 切断链表,并保存后续节点
    const back = rightP.next;
    rightP.next = null; // 将子链表尾部切断,方便反转

    // 5. 反转子链表
    const reversedHead = reserveList(leftP);

    // 6. 将反转后的子链表拼回原链表
    font.next = reversedHead;      // 前驱节点 -> 新头
    leftP.next = back;             // 新尾(原头) -> 后续节点

    // 7. 返回新的链表头(虚拟头节点的下一个)
    return dummy.next;
};

const reserveList = function(head){
    let pre = null;
    let curr = head;
    while(curr){
        const next = curr.next;
        curr.next = pre;
        pre = curr;
        curr = next;
    }
    return pre;
}

我的总结:

这段代码通过一系列精妙的指针操作,完美地实现了链表的部分反转。它首先引入一个虚拟头节点,优雅地统一了处理所有边界情况,特别是当需要从链表头部开始反转时。接着,它通过两次循环,精准地定位到了待反转区间的前驱节点、头节点和尾节点。在反转之前,它先切断了子链表与后续部分的连接,确保反转操作不会影响到链表的其他部分。然后,它调用一个标准的反转函数,将独立的子链表进行反转。最后,也是最关键的一步,它将反转后的子链表像搭积木一样,重新拼接到原链表中:前驱节点连接新的头节点,而原头节点(现在是新尾节点)则连接到之前保存的后续节点上。整个过程逻辑清晰,环环相扣,最终通过返回虚拟头节点的下一个节点,给出了正确的结果。这是一个非常经典和健壮的链表操作范例。