题目描述
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1
输入: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,用它来遍历链表和避免不必要的麻烦,这是因为当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的后置节点
- 我们挖出这个链表后,在对其反转前,需要先切断它与原链表的联系,不然链表反转之后,由于节点位置的改变,原来的链表指向将会错乱。
pre.next = null;
rightNode.next = null;
- 这里我们开始最重要的一步——反转链表。 首先,设置一个 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)
- 挖出的那部分的链表反转完成后,我们要将其与原链表接上,所以让pre.next 指向 新链表的尾部,新链表的头部指向 suc
pre.next = rightNode;
leftNode.next = suc;
- 返回反转链表,这里不再需要顶部的虚拟节点,指向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,再刷这道,对自己解出这道题有不少的帮助,在刷完这两道题后,自己对链表的理解和链表的操作更加的深入也收获了不少,所以希望这篇文章也能对你有所帮助,如果觉得文章对你有帮助的话欢迎点个小赞哦