一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情
92. 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
如果是对整个链表进行反转: 反转链表
多次扫描
已经知道如何对整个反转链表的,很容易想到的方式是:
- 通过
left和right找到需要反转链表区间的前一个节点prev和后一个节点last; - 然后通过 反转
前一个节点的next节点 完成对区间反转; - 反转完成之后,拼接之前的节点:前一个节点的
next指向反转之后的头节点,反转之后的尾结点拼接保存的后一个节点。
这里有两个个地方需要注意:
-
left可能等于1,也就是说有可能是从头节点开始反转的,那么这个时候就需要考虑反转之后要不要做头节点的拼接,这也涉及到反转之后的头节点是原来的头节点还是反转区间的头节点当链表反转或者其他操作过程中有可能修改头节点,那么有一种通用的方法可以解决,哨兵节点, 在头节点之前加一个前驱节点,然后处理该节点的next节点,处理完成之后也返回哨兵节点的next节点作为结果
-
在保存
last的时候,需要切断他与之前节点的关联,否则反转之后可能形成环形链表。
// 生成一个空的节点,将节点的next执行原本的head,接下来只处理
const dummy = new ListNode(null, head)
完整代码
function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
if (head === null || head.next === null) return head
// 哨兵节点
const dummy = new ListNode(-1, head)
let cur = dummy, idx = 0, prev = null, last = null
// 找对前一个和后一个节点
while(cur) {
if ((idx + 1) === left) {
prev = cur
}
if (idx === right) {
last = cur.next
cur.next = null
}
idx++
cur = cur.next
}
const result = reverseList(prev.next)
// 拼接前一个节点
prev.next = result
if (last) {
cur = result
// 找到反转区间的尾结点
while(cur && cur.next !== null) {
cur = cur.next
}
// 拼接原链表的后一个节点
cur.next = last
}
// 返回 哨兵节点
return dummy.next
};
一次扫描
一次扫描最重要的是对节点进行反转。这里以1 -> 2 -> 3 -> 4 -> 5,left = 2, right = 4为例。
- 找到要反转的
节点2,设节点2为cur,前一个节点为pre,下一个节点为next - 然后按照第二条链表,进行以下变换:
let next = cur.next
// 1.
cur.next = next.next
// 2.
next.next = pre.next
// 3.
pre.next = next
3.之后将第二条的链表拉直,会变成第三条,此时节点3和节点2已交互位置
- 保持
pre,cur位置不变,重复第二步中的反转,具体可以见下图:
- 再拉直就可以得到结果。
单次扫描完成最重要的就是第二步中的交换位置,具体代码如下:
function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
const dummy = new ListNode(-1, head)
let pre = dummy
// 找到pre节点
for (let i = 0; i < left - 1; i++) {
pre = pre.next
}
// cur节点
let cur = pre.next;
for (let i = 0; i < right - left; i++) {
const next = cur.next
if (next) {
cur.next = next.next
next.next = pre.next
pre.next = next
}
}
return dummy.next
}