链表K个一组翻转:迭代与递归解法详解

发布时间:2026/10/24 8:49:07
链表K个一组翻转:迭代与递归解法详解

1. 题目解析与核心思路

这道题目要求我们实现一个链表的K个一组翻转操作。具体来说,给定一个链表和一个正整数K,我们需要将链表每K个节点作为一组进行翻转,如果剩余节点不足K个则保持原有顺序。这是链表操作类题目中非常经典的题型,也是面试中的高频考点。

链表翻转本身并不复杂,但加入K个一组的限制后,需要考虑的边界条件就多了起来。我们先来看一个具体例子:

假设链表为 1->2->3->4->5,K=2 那么翻转后应该得到 2->1->4->3->5

如果K=3,结果应该是 3->2->1->4->5

1.1 问题分解

解决这个问题可以分解为以下几个子任务:

  1. 遍历链表,找到K个一组的子链表
  2. 对每个子链表进行翻转操作
  3. 将翻转后的子链表重新连接回原链表
  4. 处理不足K个节点的剩余部分

1.2 关键难点

这道题的主要难点在于:

  • 如何高效地找到K个节点的边界
  • 翻转操作时如何正确处理指针关系
  • 如何将翻转后的子链表无缝衔接回原链表
  • 处理各种边界情况(空链表、K=1、链表长度不是K的整数倍等)

2. 解决方案设计与实现

2.1 迭代解法

最直观的解法是使用迭代方法。我们可以维护几个关键指针:

  • pre:指向当前待翻转子链表的前驱节点
  • start:当前子链表的起始节点
  • end:当前子链表的结束节点
  • next:当前子链表的后继节点

具体步骤如下:

  1. 初始化dummy节点,pre指向dummy
  2. 遍历链表,找到K个节点作为一组
  3. 记录这组的start和end节点,以及下一组的start节点(next)
  4. 断开当前组与后续节点的连接
  5. 翻转当前组
  6. 将翻转后的子链表接回原链表
  7. 更新pre指针,继续处理下一组
def reverseKGroup(head, k): dummy = ListNode(0) dummy.next = head pre = dummy while head: # 找到当前组的end节点 end = pre for _ in range(k): end = end.next if not end: return dummy.next # 记录下一组的起点 next_group = end.next # 断开当前组 end.next = None # 翻转当前组 start = pre.next pre.next = reverse(start) # 重新连接 start.next = next_group # 更新指针 pre = start head = next_group return dummy.next def reverse(head): prev = None curr = head while curr: next_node = curr.next curr.next = prev prev = curr curr = next_node return prev

2.2 递归解法

递归解法更加简洁,思路是:

  1. 找到当前K个节点的末尾
  2. 如果不足K个,直接返回当前头节点
  3. 翻转前K个节点
  4. 递归处理剩余链表
  5. 将翻转后的头节点与递归结果连接
def reverseKGroup(head, k): # 找到第k+1个节点 curr = head count = 0 while curr and count < k: curr = curr.next count += 1 # 如果还有k个节点,继续递归 if count == k: # 反转前k个节点 reversed_head = reverse(head, k) # 递归处理剩余链表并连接 head.next = reverseKGroup(curr, k) return reversed_head return head def reverse(head, k): prev = None curr = head while k > 0: next_node = curr.next curr.next = prev prev = curr curr = next_node k -= 1 return prev

3. 复杂度分析与优化

3.1 时间复杂度

两种解法的时间复杂度都是O(n),其中n是链表长度。因为每个节点只会被访问固定次数(查找边界时一次,翻转时一次)。

3.2 空间复杂度

  • 迭代解法:O(1)额外空间
  • 递归解法:O(n/k)栈空间(递归深度)

3.3 优化思路

在实际编码中,迭代解法通常是首选,因为:

  1. 避免了递归的栈空间开销
  2. 更容易理解和调试
  3. 对于超长链表不会出现栈溢出问题

4. 边界条件与测试用例

4.1 常见边界情况

  1. 空链表
  2. K=1(相当于不翻转)
  3. 链表长度小于K
  4. 链表长度正好是K的整数倍
  5. 链表长度不是K的整数倍
  6. K等于链表长度(相当于整个链表翻转)

4.2 推荐测试用例

# 常规情况 [1,2,3,4,5], k=2 → [2,1,4,3,5] [1,2,3,4,5], k=3 → [3,2,1,4,5] # 边界情况 [], k=1 → [] [1], k=1 → [1] [1,2], k=2 → [2,1] [1,2,3,4,5], k=5 → [5,4,3,2,1] [1,2,3,4,5,6,7], k=3 → [3,2,1,6,5,4,7]

5. 常见错误与调试技巧

5.1 常见错误

  1. 翻转后忘记重新连接链表
  2. 指针更新顺序错误导致链表断裂
  3. 没有正确处理不足K个节点的情况
  4. 翻转时改变了不该改变的指针
  5. 边界条件处理不完整

5.2 调试技巧

  1. 画图辅助理解指针变化
  2. 对短链表进行手动推演
  3. 添加打印语句跟踪指针变化
  4. 使用IDE的调试功能逐步执行
  5. 先处理常规情况再考虑边界

提示:在翻转子链表时,建议先单独实现和测试翻转函数,确保其正确性后再集成到主逻辑中。

6. 实际面试中的应用

6.1 面试考察点

这道题在面试中主要考察:

  1. 对链表操作的熟练程度
  2. 指针操作的准确性
  3. 边界条件的处理能力
  4. 代码的整洁度和可读性
  5. 时间/空间复杂度分析能力

6.2 面试回答策略

  1. 先明确问题要求和边界条件
  2. 提出暴力解法并分析不足
  3. 逐步优化解法
  4. 讨论时间/空间复杂度
  5. 给出测试用例

6.3 代码风格建议

  1. 使用有意义的变量名(如prev, curr, next)
  2. 适当添加注释解释关键步骤
  3. 保持函数单一职责原则
  4. 处理所有可能的边界情况
  5. 先写伪代码再实现细节

7. 相关题目拓展

掌握这道题后,可以尝试以下变种题目:

  1. 从后往前K个一组翻转链表
  2. 交替K个节点翻转(如先翻转K个,接着保留K个,再翻转K个)
  3. 分组反转字符串中的单词
  4. 反转链表的一部分(指定起始和结束位置)
  5. 旋转链表(整体右移K个位置)

8. 个人经验分享

在实际编码中,我发现以下几点特别重要:

  1. 使用dummy节点可以极大简化头节点的处理
  2. 在翻转子链表前先断开它与后续节点的连接,避免指针混乱
  3. 翻转后记得重新连接链表,否则会导致部分节点丢失
  4. 对于递归解法,要注意递归终止条件的正确性
  5. 在面试中,可以先讨论思路获得反馈再开始编码

这道题的迭代解法虽然代码量稍大,但实际应用中更为可靠。我建议先掌握迭代解法,再学习递归解法作为补充。在LeetCode上提交时,要注意测试各种边界情况,确保代码的健壮性。