LeetCode 链表交换节点问题(24、25题)
本文将详细讲解 LeetCode 中两道经典的链表节点交换问题:
- 第 24 题:两两交换链表中的节点
- 第 25 题:K 个一组翻转链表
这两道题都属于链表操作的经典题目,考察了对链表指针操作的掌握程度,以及递归或迭代思想的应用。
24. 两两交换链表中的节点
题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
1 | 输入:head = [1,2,3,4] |
提示:
- 链表中节点的数目在范围
[0, 100]内 0 <= Node.val <= 100
解题思路
这道题的最优解法是使用 迭代 方法,通过维护几个关键指针来实现两两节点的交换。
核心思路:
- 使用一个虚拟头节点(dummy node)简化边界情况处理
- 维护一个当前指针
curr,用于遍历链表 - 每次交换
curr后面的两个节点 - 更新指针位置,继续下一轮交换
算法步骤:
- 创建虚拟头节点
dummy,使其指向原链表头 - 初始化当前指针
curr指向dummy - 当
curr后面有至少两个节点时:- 保存要交换的两个节点
node1和node2 - 执行交换操作:
node1.next = node2.next,node2.next = node1 - 更新
curr.next指向交换后的新头节点node2 - 将
curr移动到node1,准备下一轮交换
- 保存要交换的两个节点
- 返回虚拟头节点的下一个节点
代码实现
1 | /** |
代码讲解
- 边界处理:如果链表为空,直接返回
null - 创建虚拟头节点:
dummy节点的next指向原链表头,用于简化边界情况 - 初始化当前指针:
curr指向dummy,作为当前交换位置的前一个节点 - 循环条件:当
curr后面有至少两个节点时,继续交换 - 保存节点:
node1是当前要交换的第一个节点,node2是第二个节点 - 交换操作:
node1.next = node2.next:将node1指向node2原来的下一个节点node2.next = node1:将node2指向node1,完成两个节点的交换
- 更新指针:
curr.next = node2:将当前指针的下一个节点指向交换后的新头节点node2curr = node1:将当前指针移动到node1,准备下一轮交换
- 返回结果:返回虚拟头节点的下一个节点,即交换后的链表头
复杂度分析
- 时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次
- 空间复杂度:O(1),只使用了常数级别的额外空间
25. K 个一组翻转链表
题目描述
给你链表的头节点 head,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例:
1 | 输入:head = [1,2,3,4,5], k = 2 |
提示:
- 链表中的节点数目在范围
[0, 5000]内 0 <= Node.val <= 10001 <= k <= 5000
解题思路
这道题是第 24 题的升级版,需要以 k 个节点为一组进行翻转。同样可以使用 迭代 方法来解决。
核心思路:
- 使用虚拟头节点简化边界情况
- 维护三个指针:
prev(当前组的前一个节点)、curr(当前节点)、succ(当前组的后一个节点) - 先检查剩余节点是否足够
k个 - 如果足够,对当前组的
k个节点进行翻转 - 更新指针位置,继续下一组翻转
算法步骤:
- 创建虚拟头节点
dummy,使其指向原链表头 - 初始化
prev指向dummy,curr指向原链表头 - 循环处理链表:
- 检查剩余节点是否足够
k个 - 如果不足,跳出循环
- 如果足够,找到当前组的尾节点和下一组的头节点
- 翻转当前组的
k个节点 - 更新指针位置,连接翻转后的链表
- 检查剩余节点是否足够
- 返回虚拟头节点的下一个节点
代码实现
1 | /** |
代码讲解
- 边界处理:如果链表为空,直接返回
null - 创建虚拟头节点:
dummy节点的next指向原链表头 - 初始化指针:
prev指向dummy,curr指向原链表头 - 检查剩余节点:
- 使用
succ指针从prev开始移动k步 - 如果移动过程中遇到
null,说明剩余节点不足k个,跳出循环
- 使用
- 记录位置:
succ指向当前组的下一个节点start是当前组的前一个节点end是当前组翻转前的头节点(翻转后的尾节点)
- 翻转当前组:
- 使用
prev和curr指针进行翻转 - 每次将
curr的下一个节点指向prev,然后更新prev和curr
- 使用
- 连接链表:
start.next = prev:将当前组的前一个节点指向翻转后的头节点end.next = succ:将翻转后的尾节点指向原当前组的下一个节点
- 更新指针:
prev = end:将prev指向翻转后的尾节点curr = prev.next:将curr指向下一组的头节点
- 返回结果:返回虚拟头节点的下一个节点
复杂度分析
- 时间复杂度:O(n),其中 n 是链表的长度。每个节点最多被访问两次
- 空间复杂度:O(1),只使用了常数级别的额外空间
总结
这两道题都是链表操作的经典题目,考察了对链表指针的熟练掌握程度。它们的共同特点是:
- 使用 虚拟头节点 简化边界情况处理
- 维护多个指针来跟踪当前操作的位置
- 进行节点的实际交换,而不是仅仅改变节点的值
- 保持指针的正确指向,避免链表断裂
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 ZALA!

