
本文详解 reverse_between(start_index, end_index) 方法的实现原理,重点剖析 dummy 节点的作用、previous_node 的定位逻辑,以及三步指针重连的核心技巧,帮助你彻底理解链表区间反转的底层机制。
本文详解 `reverse_between(start_index, end_index)` 方法的实现原理,重点剖析 dummy 节点的作用、`previous_node` 的定位逻辑,以及三步指针重连的核心技巧,帮助你彻底理解链表区间反转的底层机制。
在链表操作中,原地反转指定索引范围的节点(如反转第 2 到第 3 个节点)是一个经典且易错的题目。其难点不在于“如何翻转”,而在于如何安全、精准地锚定边界并避免断链。下面我们将结合完整逻辑与可视化推演,逐层拆解 reverse_between 方法。
? 关键前提:索引从 0 开始,且 start_index 和 end_index 均为有效位置(0 ≤ start_index ≤ end_index
以示例链表 1 → 2 → 3 → 4 → 5 为例,调用 reverse_between(2, 3) 表示反转索引 2 和 3 对应的节点(即值为 3 和 4 的节点),期望结果为 1 → 2 → 4 → 3 → 5。
✅ 第一步:引入 dummy 节点,统一边界处理
dummy_node = Node(0) dummy_node.next = self.head previous_node = dummy_node
- dummy_node 是一个哨兵节点(sentinel),它不存储业务数据,仅用于简化头节点参与反转时的特殊逻辑。
- 将 previous_node 初始化为 dummy_node,意味着它始终指向待反转区间的前驱节点——这是整个算法的“锚点”。
? 为什么不用 self.head 直接开始?因为当 start_index == 0 时,头节点本身要被移动,若无 dummy,需额外判断并更新 self.head,代码分支复杂。引入 dummy 后,无论 start_index 是否为 0,previous_node 总能通过相同逻辑抵达“反转起始位置的前一个节点”。
✅ 第二步:精确定位 previous_node —— 它应在 start_index - 1 处
for i in range(start_index):
previous_node = previous_node.next
- 此循环执行 start_index 次,使 previous_node 从 dummy 出发,向前移动 start_index 步。
- 注意:dummy 位于索引 -1(逻辑位置),移动 1 步到达索引 0(头节点),移动 2 步到达索引 1,依此类推。
- 因此,循环结束后,previous_node 恰好停在索引为 start_index - 1 的节点上(即待反转段的前一个节点)。
✅ 示例验证(start_index = 2):
- 初始:previous_node → dummy (idx=-1)
- 第 1 次迭代:→ node(1) (idx=0)
- 第 2 次迭代:→ node(2) (idx=1)
→ 此时 previous_node 指向值为 2 的节点,正是索引 1(即 start_index - 1)的位置,完美满足要求。
✅ 第三步:启动反转——使用“头插法”逐个迁移节点
current_node = previous_node.next # 指向反转段首节点(索引 start_index)
for _ in range(end_index - start_index):
node_to_move = current_node.next # 取出下一个待插入节点
current_node.next = node_to_move.next # 断开连接
node_to_move.next = previous_node.next # 插入到 previous_node 之后
previous_node.next = node_to_move # 更新 previous_node 的 next 指针
该循环执行 end_index - start_index 次(本例中为 3 - 2 = 1 次),本质是将 current_node 后的每个节点,依次“剪切”并“头插”到 previous_node 后面,从而实现局部逆序。
? 一次迭代的四步操作解析(以 1→2→3→4→5,start=2, end=3 为例):
- node_to_move = current_node.next → node_to_move 指向 4
- current_node.next = node_to_move.next → 3.next = 5,链表变为 1→2→3→5
- node_to_move.next = previous_node.next → 4.next = 3(previous_node 是 2,2.next 原为 3)
- previous_node.next = node_to_move → 2.next = 4,最终形成 1→2→4→3→5
⚠️ 注意顺序不可颠倒!必须先保存 node_to_move.next,再修改 current_node.next,否则会丢失后续节点引用。
✅ 第四步:更新头节点(仅当反转包含原 head 时生效)
self.head = dummy_node.next
- dummy_node.next 始终指向当前逻辑上的新头节点。
- 若 start_index == 0,反转后原 head 已被移走,dummy.next 指向新头,此赋值必不可少;
- 若 start_index > 0(如本例),dummy.next 仍是原 head,赋值无副作用,但保证了代码健壮性。
? 完整可运行示例(含辅助类)
class Node:
def __init__(self, value):
self.value = value
self.next = None
class LinkedList:
def __init__(self, value):
self.head = Node(value)
self.length = 1
def append(self, value):
new_node = Node(value)
current = self.head
while current.next:
current = current.next
current.next = new_node
self.length += 1
def reverse_between(self, start_index, end_index):
if self.length <h3>✅ 总结:三个核心设计思想</h3>
| 思想 | 说明 | 价值 |
|---|---|---|
| 哨兵节点(Dummy) | 在链表头部添加虚拟节点 | 消除头节点特殊处理,使所有反转场景逻辑一致 |
| 前驱定位(previous_node) | 通过 start_index 次移动抵达 start_index - 1 位置 | 精准控制插入位置,避免越界或断链 |
| 头插式反转 | 每次将 current.next 节点“剪切”后插入 previous.next | 无需额外空间,O(1) 空间复杂度,稳定高效 |
掌握这三点,你不仅能写出正确的 reverse_between,更能举一反三,应对各类链表区间操作题(如删除、分割、合并等)。真正的数据结构能力,始于对指针关系的敬畏与精确掌控。











