92 反转链表 II

276 阅读3分钟

题目描述

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

分析

输入:链表头节点 head
输出:返回翻转之后的链表头节点

解题思路

如果我们有一个链表从 1 到 5,left 在 2, right 在 4,我们需要翻转的部分在 2-4 之间这段

image.png

在这里我们也需要记录下这个区间的前驱节点 pre, 后继节点 succ。这是为了翻转之后,把前驱节点的 next 和翻转之后的这部分的最后一个节点的 next 指向正确的节点。

这里需要注意的一点是,left, right,只是一个 index,并没有直接给我们真实的节点啊。所以还需要我们自己去找这两个节点,我把他们命名为 leftNode, rightNode, 以及 pre, succ, 才能做对应的操作。

那实际上就是要找 pre, rightNode, leftNode, succ 就是他们的 next 指向。

为了少做判断,我们引入一个 dummyHead,将它的 next 指向 head。我们的查找过程从 dummyHead 开始。

OK,那先找 pre, 现在问题的关键就是,要从 dummyHead 移动几步,能够得到正确的节点?

这个过程我建议大家用 for 循环来实现,因为你要走的步数,完全可以作为 for 终止的条件,我们举个例子🌰:

比如我要走两步,

for (let i = 0; i < 2; i++) {
    console.log(`I have moved '${i + 1}' steps`)
}

很明显上面 for 循环的语句执行了两次,那如果每次变量都在链表里走一步,就会移动移动两步。可以证明这个过程是正确的。

我们运用这个原理去观察 leftpre 的对应关系。

preleft 差 1,如果 left 在 2, 那么找到 pre 的过程是这样的:

dummyHead ➡️ 1(head)

实际上就是走一步,left - 1

那么找到 pre 的 code 就是:

for (let i = 0; i < left - 1; i++) {
    pre = pre.next
}

那对于 rightNode,我们如法炮制,观察一下 rightpre

right 在 4, prev 在 1, rightleft 差2,

那么对于已经找到的 pre, 需要走 right - left + 1

对应的代码就是:

rightNode = pre
for (let i = 0; i < right -left + 1; i++) {
    rightNode = rightNode.next
}

找到了他们,我们也就找到了 leftNode, succ

对于翻转的过程,我们也有一个固定的模版去解决:

function reverseList(head) {
    // 拿前一个节点和当前节点进行位置交换
    let pre = null,
        cur = head
        
    // 只要还有节点要交换,就进行下去
    while (cur) {
        // 因为要把当前节点的 next 指向前一个节点,所以用一个变量先把当前的 next 存起来~
        const tmp = cur.next
        cur.next = pre
        pre = tmp
        
        cur = cur.next
    }
}

最后再把这个反转后的链表和外边的剩余部分连接起来就行了,也就是和 pre, succ 连接起来。

我们直接看整体的代码

代码

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
/**
 * @param {ListNode} head
 * @param {number} left
 * @param {number} right
 * @return {ListNode}
 */
var reverseBetween = function (head, left, right) {
  let leftNode, rightNode, pre, succ;
  const dummyHead = new ListNode(null, head);
  pre = dummyHead;

  for (let i = 0; i < left - 1; i++) {
    pre = pre.next;
  }

  rightNode = pre;
  for (let i = 0; i < right - left + 1; i++) {
    rightNode = rightNode.next;
  }

  succ = rightNode.next;
  leftNode = pre.next;
  pre.next = null;
  rightNode.next = null;

  reverseList(leftNode);
  function reverseList(head) {
    let prev = null;
    let cur = head;

    while (cur) {
      const tmp = cur.next;

      cur.next = prev;
      prev = cur;
      cur = tmp;
    }
  }

  pre.next = rightNode;
  leftNode.next = succ;

  return dummyHead.next;
};

复杂度

时间:O(N), 最多两次遍历全体链表,如果需要反转的部分接近了整体长度
空间:O(1),只需要保存几个指针