[LeetCode92题反转链表II] | 刷题打卡

250 阅读5分钟

题目描述

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

示例 1

image.png

输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5] 提示: 链表中节点数目为 n

  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

思路分析

题目分析

我们看到题目要求,按照指定的left 和 right划定出链表范围,对这个范围中的链表进行反转,这就要求我们在解题时首先要取到left 和 right的位置,然后对left 和 right 之间的链表进行反转,反转之前要跟left前的结点和right后的节点进行 联系切除操作,反转完成后再恢复。下面让我们来看一下具体的步骤吧

具体步骤

  1. 我们需要设置一个在头部之上的虚拟节点,位置为-1,用它来遍历链表和避免不必要的麻烦,这是因为当head是left时,会产生遍历的麻烦,所以这里直接使用一个顶部虚拟节点 topNode ,topNode.next 指向head。
const topNode = new ListNode(-1);
topNode.next = head;

2.确定left和它的前置节点的位置以及right和它的后置节点的位置 设置一个 pre,我们想要他指向left的前置节点,用for循环找到它的位置

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

设置一个leftNode节点,指向left的位置

let leftNode = pre.next;

此时,left的位置已经得到,我们继续获取right的位置 获取right位置时可以做一下优化,不用再从顶部节点进行遍历,我们可以从leftNode的位置继续进行遍历

let rightNode = leftNode;
  for (let i = 0; i < right - left; i++) {// 此处用right-left,是优化性能,往rightNode不用在重复走leftNode走过的结点
    rightNode = rightNode.next;
  }
let suc = rightNode.next;

最后,设置suc节点指向right的后置节点

  1. 我们挖出这个链表后,在对其反转前,需要先切断它与原链表的联系,不然链表反转之后,由于节点位置的改变,原来的链表指向将会错乱。
pre.next = null;
rightNode.next = null;
  1. 这里我们开始最重要的一步——反转链表。 首先,设置一个 prev节点用于备用,然后设置一个node头节点。 重点来了,我们首先设置一个next指向node.next,这一步是为了防止node在改变指向之后,找不回原来的位置。我们继续将node的指向转变为它的前置节点完成指向转变
node.next = prev;

转变完成后,我们继续将prev和node向前推进,由于next记录了node.next 的位置,所以node直接跳转。至此,完成了两个节点之间的指向反转

prev = node; 
node = next;

代码

while(node) { // 如果还有node,则继续反转指向
    const next = node.next // 先存好node的后置节点,完成反转后再把 node 接过来
    node.next = prev; // 反转
    prev = node; // 推进前置节点
    node = next;// node 推进,至后置节点。 到这里,完成了两个节点的反转
}

我们设置一个while循环将node不断推进,对链表进行反转的操作,最终遍历完成后,我们将得到一个反转的链表

var reverseList = function(head) {
  let prev = null;
  let node = head;
  while(node) { // 如果还有node,则继续反转指向
    const next = node.next // 先存好node的后置节点,完成反转后再把 node 接过来
    node.next = prev; // 反转
    prev = node; // 推进前置节点
    node = next;// node 推进,至后置节点。 到这里,完成了两个节点的反转
  }
return prev;  // 反转完链表后, prev才是头结点
};

我们将这部分代码封装成一个方法进行抛出,调用这个方法,进行链表反转

 // 3 反转链表
  reverseList(leftNode)
  1. 挖出的那部分的链表反转完成后,我们要将其与原链表接上,所以让pre.next 指向 新链表的尾部,新链表的头部指向 suc
  pre.next = rightNode;
  leftNode.next = suc;
  1. 返回反转链表,这里不再需要顶部的虚拟节点,指向next
return topNode.next

代码

const reverseList = function(head) {
  let prev = null;
  let node = head;
  while(node) {
    const curr = node.next;
    node.next = prev;
    prev = node;
    node = curr;
  }
  return node;
}

var reverseBetween = function(head, left, right) {
  // 1 设置顶部虚拟节点
  const topNode = new ListNode(-1);
  topNode.next = head;

  // 2 确定left 和 right位置,以及前后置节点
  let pre = topNode;;
  for (let i = 0; i < left -1; i++) {
    pre = pre.next;
  }
  let leftNode = pre.next;
  
  let rightNode = leftNode;
  for (let i = 0; i < right - left; i++) {// 此处用right-left,是优化性能,往rightNode不用在重复走leftNode走过的结点
    rightNode = rightNode.next;
  }
  let suc = rightNode.next;

  // 3 切断联系
  pre.next = null;
  rightNode.next = null;

  // 4 反转链表
  reverseList(leftNode)

  // 5 恢复联系
  pre.next = rightNode;
  leftNode.next = suc;
  // 6
  return topNode.next; // 注意,这里返回的是next,后面得到结点,直接返回topNode会把-1也带上
}

总结

我们对一个限定范围的链表进行反转,首先需要确定好它的头与尾,然后再对这个链表进行反转,反转前后也要进行与原链表的联系切除的操作,最后完成反转后,再进行联系恢复,就能得到一个指定范围反转的链表了。

这道题是在206.反转链表这道题上进行进阶的,我之前是先刷完206,再刷这道,对自己解出这道题有不少的帮助,在刷完这两道题后,自己对链表的理解和链表的操作更加的深入也收获了不少,所以希望这篇文章也能对你有所帮助,如果觉得文章对你有帮助的话欢迎点个小赞哦