前言
以三种迭代的方式来解决这道题
- 拆合:拆成 (左 + 普通反转 + 右),反转后,连接起来
- 拆合:拆成(左 + 反转前n个),反转后,连接起来
- 头插法:我理解就是 (存、删、增)
一、题目描述
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表
二、解题
2.1 方法一、拆合(左 + 普通反转 + 右)
2.1.1 思路
- 分成三部分 - 左、普通反转、右
- 记录四个节点 待反转头节点、待反转头节点的前一个节点、待反转反转尾节点、待反转尾节点的后一个节点
- 反转需要反转的部分,反转后,头尾位置互换,这里划重点
- 拼接:待反转头节点的前一个节点 + 待反转反转尾节点(即反转后反转部分的头节点),待反转头节点的前一个节点(即反转后反转部分的尾节点) + 待反转尾节点的后一个节点
2.1.2 代码
const reverseBetween = function (head, left, right) {
// 虚拟头节点 减少边界判断
let hair = new ListNode(-1, head)
// 反转头节点的前一个节点
let pre = hair
// 找到pre
for(let i = 0; i < left - 1; i++) {
pre = pre.next
}
// 反转尾节点
let end = pre
// 找到end
for(let i = 0; i < right - left + 1; i++) {
end = end.next
}
// 反转头节点
const start = pre.next
// 反转尾节点的后一个节点
const suc = end.next
// 断开连接
pre.next = null
end.next = null
// 反转
reverse(start)
// 反转后的拼接
pre.next = end
start.next = suc
// 返回
return hair.next
}
// 普通反转
function reverse(head) {
// 空链表和只有一个节点的链表不需要反转
if(head === null || head.next === null) return false
let pre = null
let cur = head
while(cur !== null) {
const next = cur.next
cur.next = pre
pre = cur
cur = next
}
}
2.2 方法二、拆合(左 + 反转前n个)
2.2.1 思路
这个方法与方法一差不多,不想过多介绍。只不过这个方法拆成了两个部分,然后拼接,具体就直接看我代码注释吧
2.2.2 代码
// 解法二:拆合-拆成两部分(left前面 + 反转前n个节点)与解法一实际差不多
const reverseBetween = function (head, left, right) {
// 虚拟头节点,减少边界判断
let hair = new ListNode(-1, head)
// 反转头节点 的 前一个节点
let pre = hair
// 计数,left 后 反转前n 个节点
const n = right - left + 1
// 找到pre
while(left - 1 > 0) {
pre = pre.next
left--
}
// 反转前n个节点,返回反转头
let p = reverse(pre.next, n)
// 拼接 pre + 反转头
pre.next = p
// 返回结果
return hair.next
}
// 返回头节点
function reverse(head, n) {
let pre = null
// 记录未反转的头
let cur = head
// 反转前n个节点
while(n > 0) {
const next = cur.next
cur.next = pre
pre = cur
cur = next
n--
}
// 此时head 是 反转部分的尾,cur 是 反转尾的下一个节点
head.next = cur
return pre
}
2.3 方法三、头插法
2.3.1 思路
- 找到待反转部分头节点的前一个节点,记录
- 依次遍历反转部分节点,将每个节点插到头部
- 具体步骤:存、删、增
- 存:目的是保证删后不丢失
- 删:把当前节点的下一个节点删掉
- 增:把保存的节点即刚才删掉的节点直接插入到反转的头部
2.3.2 代码
const reverseBetween = function (head, left, right) {
// 虚拟头节点 ,用来减少边界判断
let hair = new ListNode(-1, head)
// 反转部分头节点前一个节点
let pre = hair
for(let i = 0; i < left - 1; i++) {
pre = pre.next
}
// 待反转头部
let cur = pre.next
// 头插法,将 next 插到 反转部分的头
for(let i = 0; i < right - left + 1; i++) {
// 保存next
const next = cur.next
// 删除next
cur.next = next.next
// 往反转头增加next
next.next = pre.next
// 拼接
pre.next = next
}
return hair.next
}