文章目录
- [92. 反转链表 II](#92. 反转链表 II)
92. 反转链表 II
题目描述
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
- 链表中节点数目为 n
- 1 <= n <= 500
- -500 <= Node.val <= 500
- 1 <= left <= right <= n
进阶: 你可以使用一趟扫描完成反转吗?
解题思路
问题深度分析
本题要求在单链表中仅反转[left, right]区间节点,其他部分保持原相对顺序,时间O(n)、空间O(1)的一趟扫描是最优思路。核心在于:精确定位边界、断开/接回指针、在区间内进行原地反转。
- 关键指针:
pre(指向left前一个结点)、leftNode(left处结点)、rightNode(right处结点)、rightNext(right后一个结点)。 - 一趟扫描常用"头插法"在区间内原地重排;或使用常规反转再接回。
核心思想对比
- 方法一(头插法一趟扫描,最优):固定
pre,每次把leftNode后面的结点摘下,插到pre之后,实现区间内前插,O(1)空间,O(n)时间。 - 方法二(常规反转再接回):先走到
pre与rightNext,断开[left,right],常规反转,再接回,思路直观。 - 方法三(递归:反转前N个 + 偏移):将问题转为"反转链表前N个"子问题,向右偏移left-1次,递归优雅但实现需小心边界。
- 方法四(栈/数组辅助):将区间内节点压栈后再弹出拼接,易写但需O(k)额外空间(k=right-left+1)。
算法流程图
主流程(以头插法为例)
哑结点dummy->head 定位pre到left前一位 leftNode=pre.next i从0到right-left-1 取出next := leftNode.next leftNode.next = next.next next.next = pre.next pre.next = next 完成, 返回dummy.next
常规反转+接回
dummy->head 找到pre与rightNode rightNext=rightNode.next 断开pre.next..rightNode子链 反转子链 pre.next=反转后的头 原leftNode.next=rightNext 返回dummy.next
递归思路(反转前N个)
复杂度分析
- 时间复杂度:四种方法均为O(n),n为链表长度。
- 空间复杂度:
- 头插法与常规反转:O(1)
- 递归:O(right-left+1) 递归栈
- 栈法:O(right-left+1) 额外空间
关键边界与陷阱
- left==right:无需操作直接返回。
- left==1:
pre为dummy,注意连接。 - 只有一个节点/空链表:直接返回。
- 区间在尾部:
rightNext可能为nil,接回时谨慎。 - 指针顺序:先保存
next再修改指针,避免丢链。
方法与代码要点(Go)
- 方法一:头插法一趟扫描(推荐最优)
- 固定
pre与leftNode,把leftNode后面的节点依次插到pre后面。
- 固定
- 方法二:常规反转子链后接回
- 先切出子链段,再常规反转,然后两端接回。
- 方法三:递归reverseN
reverseBetween(head,l,r):若l==1,调用reverseN(head,r);否则head.next = reverseBetween(head.next,l-1,r-1)。
- 方法四:栈辅助
- 遍历区间压栈,随后弹栈重建区间,最后接回。
测试用例设计
-
1,2,3,4,5\], left=2, right=4 -\> \[1,4,3,2,5
-
5\], left=1, right=1 -\> \[5
-
1,2,3\], left=1, right=3 -\> \[3,2,1
-
1,2,3,4\], left=3, right=4 -\> \[1,2,4,3
-
1,2\], left=1, right=2 -\> \[2,1
- 边界:空/单节点/left==right/区间含尾部
实战技巧
- 哑结点
dummy极大简化头部操作。 - 指针改动顺序:保存next -> 断开 -> 插入/反转 -> 接回。
- 模板化书写,减少出错。
完整题解代码
go
package main
import (
"fmt"
)
type ListNode struct {
Val int
Next *ListNode
}
// =========================== 方法一:头插法一趟扫描(最优) ===========================
func reverseBetween1(head *ListNode, left int, right int) *ListNode {
if head == nil || left == right {
return head
}
dummy := &ListNode{Next: head}
pre := dummy
for i := 1; i < left; i++ {
pre = pre.Next
}
leftNode := pre.Next
for i := 0; i < right-left; i++ {
next := leftNode.Next
leftNode.Next = next.Next
next.Next = pre.Next
pre.Next = next
}
return dummy.Next
}
// =========================== 方法二:常规反转子链后接回 ===========================
func reverseBetween2(head *ListNode, left int, right int) *ListNode {
if head == nil || left == right {
return head
}
dummy := &ListNode{Next: head}
pre := dummy
for i := 1; i < left; i++ {
pre = pre.Next
}
rightNode := pre
for i := 0; i < right-left+1; i++ {
rightNode = rightNode.Next
}
rightNext := rightNode.Next
// 切下子链
leftNode := pre.Next
rightNode.Next = nil
// 反转子链
revHead := reverseList(leftNode)
// 接回
pre.Next = revHead
leftNode.Next = rightNext
return dummy.Next
}
func reverseList(head *ListNode) *ListNode {
var prev *ListNode
for head != nil {
next := head.Next
head.Next = prev
prev = head
head = next
}
return prev
}
// =========================== 方法三:递归(reverseN + 偏移) ===========================
var successor *ListNode
func reverseBetween3(head *ListNode, left int, right int) *ListNode {
if left == 1 {
return reverseN(head, right)
}
head.Next = reverseBetween3(head.Next, left-1, right-1)
return head
}
func reverseN(head *ListNode, n int) *ListNode {
if n == 1 {
successor = head.Next
return head
}
last := reverseN(head.Next, n-1)
head.Next.Next = head
head.Next = successor
return last
}
// =========================== 方法四:栈辅助 ===========================
func reverseBetween4(head *ListNode, left int, right int) *ListNode {
if head == nil || left == right {
return head
}
dummy := &ListNode{Next: head}
pre := dummy
for i := 1; i < left; i++ {
pre = pre.Next
}
// 收集区间节点
stack := []*ListNode{}
node := pre.Next
for i := left; i <= right; i++ {
stack = append(stack, node)
node = node.Next
}
rightNext := node
// 从栈弹出重建
for len(stack) > 0 {
n := stack[len(stack)-1]
stack = stack[:len(stack)-1]
pre.Next = n
pre = pre.Next
}
pre.Next = rightNext
return dummy.Next
}
// =========================== 工具方法与测试 ===========================
func buildList(vals []int) *ListNode {
if len(vals) == 0 {
return nil
}
head := &ListNode{Val: vals[0]}
cur := head
for i := 1; i < len(vals); i++ {
cur.Next = &ListNode{Val: vals[i]}
cur = cur.Next
}
return head
}
func toSlice(head *ListNode) []int {
var res []int
for head != nil {
res = append(res, head.Val)
head = head.Next
}
return res
}
func equalSlice(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func runTests(methodName string, f func(*ListNode, int, int) *ListNode) int {
tests := []struct {
in []int
l, r int
want []int
label string
}{
{[]int{1, 2, 3, 4, 5}, 2, 4, []int{1, 4, 3, 2, 5}, "mid range"},
{[]int{5}, 1, 1, []int{5}, "single"},
{[]int{1, 2, 3}, 1, 3, []int{3, 2, 1}, "all"},
{[]int{1, 2, 3, 4}, 3, 4, []int{1, 2, 4, 3}, "tail"},
{[]int{1, 2}, 1, 2, []int{2, 1}, "two"},
{[]int{1, 2, 3, 4, 5}, 3, 3, []int{1, 2, 3, 4, 5}, "no-op"},
}
pass := 0
for i, tc := range tests {
head := buildList(tc.in)
out := f(head, tc.l, tc.r)
s := toSlice(out)
ok := equalSlice(s, tc.want)
status := "✅"
if !ok {
status = "❌"
}
fmt.Printf(" 测试%d(%s): %s\n", i+1, tc.label, status)
if !ok {
fmt.Printf(" 输入: %v, left=%d, right=%d\n", tc.in, tc.l, tc.r)
fmt.Printf(" 输出: %v\n", s)
fmt.Printf(" 期望: %v\n", tc.want)
} else {
pass++
}
}
fmt.Printf(" 通过: %d/%d\n\n", pass, len(tests))
return pass
}
func main() {
fmt.Println("=== LeetCode 92: 反转链表 II ===\n")
methods := []struct {
name string
fn func(*ListNode, int, int) *ListNode
}{
{"头插法一趟扫描(最优)", reverseBetween1},
{"常规反转后接回", reverseBetween2},
{"递归(reverseN)", reverseBetween3},
{"栈辅助", reverseBetween4},
}
for _, m := range methods {
fmt.Printf("方法:%s\n", m.name)
runTests(m.name, m.fn)
}
}