Skip to main content

反转链表Ⅱ

· 5 min read
Eureka X
Mr.Nobody

力扣面试经典——92题

💡 参考代码:

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/

/**
* 反转链表中指定区间内的节点
* @param head 链表头指针
* @param left 起始位置(从1开始计数)
* @param right 结束位置(从1开始计数)
* @return 反转后的链表头指针
*
* 算法思路:
* 1. 创建虚拟头节点,简化边界处理
* 2. 找到反转区间的前一个节点
* 3. 对[left, right]区间内的节点进行反转
* 4. 连接反转后的链表段与原链表的其余部分
*
* 时间复杂度: O(n),其中n是链表长度,只需一次遍历
* 空间复杂度: O(1),只使用常数额外空间
*/
struct ListNode* reverseBetween(struct ListNode* head, int left, int right) {
// 创建虚拟头节点,简化头节点可能被修改的情况
struct ListNode dummy;
dummy.next = head;

// 找到反转区间的前一个节点
struct ListNode* prev = &dummy;
for (int i = 1; i < left; i++) {
prev = prev->next;
}

// current指向反转区间的第一个节点
struct ListNode* current = prev->next;

// 头插法反转链表区间
// 将current后面的节点依次插入到prev后面
for (int i = 0; i < right - left; i++) {
struct ListNode* next = current->next; // 保存待插入节点
current->next = next->next; // 断开待插入节点
next->next = prev->next; // 连接节点右侧
prev->next = next; // 连接节点左侧
}

// 返回新的头节点
return dummy.next;
}

📖 总结:

点击展开题目总结

🤔 反转链表 II


1️⃣ 题目核心信息

🎯 功能描述:给定单链表的头指针和两个整数 left 和 right,其中 left <= right,反转从位置 left 到位置 right 的链表节点,返回反转后的链表。

📥 输入输出

  • 输入
    • head: 链表的头节点
    • left: 反转区间的起始位置(从1开始计数)
    • right: 反转区间的结束位置(从1开始计数)
  • 输出:反转指定区间后的链表头节点

2️⃣ 实现原理

💡 核心思路:使用虚拟头节点简化边界处理,通过头插法在一次遍历中完成指定区间的链表反转。

📋 实现步骤

  1. 创建虚拟头节点,指向原链表头节点,简化边界情况处理
  2. 找到反转区间的前一个节点 prev
  3. 使用头插法,将反转区间内的节点逐个插入到 prev 后面
  4. 返回虚拟头节点的下一个节点作为新链表头节点

3️⃣ 关键点解析

🎯 代码技巧

  • 使用虚拟头节点(dummy node)统一处理头节点可能被修改的情况
  • 头插法实现链表反转,避免多次遍历
  • 精确控制循环次数,只对需要反转的部分进行操作

4️⃣ 使用场景

✅ 适用情况:

  • 需要反转链表中某一段连续节点
  • 要求一次遍历完成操作的链表问题
  • 链表局部反转操作

⚠️ 前提条件:

  • 链表非空
  • 1 <= left <= right <= 链表长度

5️⃣ 复杂度分析

  • ⏱️ 时间复杂度:O(n),其中 n 是链表长度,只需一次遍历
  • 💾 空间复杂度:O(1),只使用常数额外空间

6️⃣ 注意事项

🚩 边界情况:

  • left = 1 时,头节点会发生变化
  • left = right 时,不需要反转
  • 链表只有一个节点的情况

💥 易错点:

  • 链表指针操作时容易出现空指针异常
  • 反转区间边界处理错误
  • 忘记更新连接关系导致链表断裂

7️⃣ 补充说明

以例子来逐步演示 reverseBetween 函数的执行过程。

以示例1为例:head = [1,2,3,4,5], left = 2, right = 4

初始状态

链表结构:1 -> 2 -> 3 -> 4 -> 5 -> NULL

第一步:创建虚拟头节点

dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> NULL

第二步:找到反转区间的前一个节点(prev)

循环 left-1 = 1 次,找到第1个节点(值为1)作为 prev

dummy -> 1(prev) -> 2(current) -> 3 -> 4 -> 5 -> NULL

第三步:开始头插法反转

第一次迭代 (i=0):

  • current 指向节点2
  • next 指向节点3
  • 将节点3插入到 prev 后面

操作前:

dummy -> 1(prev) -> 2(current) -> 3(next) -> 4 -> 5 -> NULL

操作后:

dummy -> 1(prev) -> 4 -> 3 -> 2(current) -> 5 -> NULL

第四步:完成反转

此时已完成区间 [2,4] 的反转,最终链表为:

dummy -> 1 -> 4 -> 3 -> 2 -> 5 -> NULL

返回 dummy.next,即节点1,得到结果链表:1 -> 4 -> 3 -> 2 -> 5 -> NULL

这个过程的关键在于通过头插法,在一次遍历中就完成了指定区间的节点反转,避免了多次遍历链表的开销。

头插法的通俗解释

可以把这个过程想象成整理书架:

  1. prev 是书架上固定不动的标记点

  2. current 是你要开始整理的那本书

  3. 每次都把 current 后面那本书(next)拿出来

  4. 把这本书插到标记点(prev)后面的第一位置

  5. 重复这个过程,直到整理完指定数量的书

这样,原本顺序排列的书就变成了倒序排列,实现了"反转"的效果。

加载评论中...