【力扣-25. k个一组翻转链表 ✨】Python笔记

0 阅读5分钟

K 个一组翻转链表:链表操作的“硬核”挑战

摘要:本文详解 LeetCode 25 题“K 个一组翻转链表”。通过“迭代法”和“递归法”两种视角,结合“虚拟头节点”与“子链表翻转”,在不修改节点值的前提下,实现每 K 个节点为一组的翻转,助你掌握链表操作的高阶技巧。


📚 核心知识点:子链表翻转与连接

这道题是链表操作的“集大成者”,它结合了反转链表链表连接两个核心技巧。

关键概念

  1. 子链表翻转:我们需要一个辅助函数,能够翻转从 startend 的这一段链表,并返回新的头节点。
  2. 连接艺术:翻转完一组后,需要将前一组的尾部连接到当前组的新头部,当前组的旧头部(翻转后的尾部)连接到下一组的头部。
  3. 虚拟头节点(Dummy Node) :处理头节点变化的必备神器,让第一组的翻转逻辑与后续组保持一致。

📝 题目解析:LeetCode 25. K 个一组翻转链表

题目描述
给你链表的头节点 head,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]


💡 迭代法:步步为营的“工程派”

核心逻辑
想象你在工厂流水线上工作。你的任务是把传送带上的零件每 k 个取下来,倒个序放回去,然后处理下一批。

步骤拆解

  1. 检查数量:首先,我们要确认后面还有没有 k 个节点。如果没有,直接返回,剩下的保持原样。

  2. 切断:找到这一组的结尾,把它和后面的链表暂时切断。

  3. 翻转:调用“反转链表”函数,翻转这一小段。

  4. 连接

    • 上一组的尾巴(prev)连接这一组的新头(new_head)。
    • 这一组的旧头(现在是尾巴)连接下一组的开头。
  5. 移动:更新 prev,准备处理下一组。

代码实现(Python)

class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        # 1. 虚拟头节点,永远指向链表头部,方便处理第一组
        dummy = ListNode(0)
        dummy.next = head
        # prev 指向每一组的前驱节点
        prev = dummy

        while head:
            # 2. 检查剩余节点是否足够 k 个
            tail = prev
            for i in range(k):
                tail = tail.next
                if not tail:
                    return dummy.next  # 不足 k 个,直接返回结果

            # 3. 记录下一组的开头,并切断当前组
            next_group_head = tail.next
            tail.next = None

            # 4. 翻转当前组 (head 到 tail),并连接回主链表
            # reverse 函数返回新的头节点(原 tail)和新的尾节点(原 head)
            new_head, new_tail = self.reverse(head, tail)

            # 连接:前驱 -> 新头
            prev.next = new_head
            # 连接:新尾 -> 下一组开头
            new_tail.next = next_group_head

            # 5. 更新指针,准备下一轮
            prev = new_tail
            head = next_group_head

        return dummy.next

    # 辅助函数:翻转从 head 到 tail 的链表,返回 (新头, 新尾)
    def reverse(self, head: ListNode, tail: ListNode):
        prev = tail.next  # 翻转后的尾节点要指向下一组的开头
        curr = head
        while prev != tail:
            next_node = curr.next
            curr.next = prev
            prev = curr
            curr = next_node
        return tail, head  # 原 tail 变新头,原 head 变新尾

🚀 递归法:四两拨千斤的“魔法派”

核心逻辑
递归的思路非常清晰,它把问题拆解为:
翻转前 K 个节点 + 剩下的节点交给递归去处理。

步骤拆解

  1. 基准情况(Base Case) :如果剩下的节点不足 k 个,直接返回 head,不做任何处理。

  2. 递归步骤

    • 找到第 k 个节点。
    • 记录第 k+1 个节点(next_group)。
    • 切断第 k 个节点和后面的联系。
    • 翻转前 k 个节点。
    • 关键点:翻转后的尾节点(也就是原来的 head)的 next,应该指向“递归处理 next_group 的结果”。

代码实现(Python)

class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        # 1. 检查剩余节点是否足够 k 个
        curr = head
        for i in range(k):
            if not curr:
                return head  # 不足 k 个,直接返回,不翻转
            curr = curr.next

        # 2. 此时 curr 指向第 k+1 个节点
        # 递归处理剩下的部分,返回的是剩下部分处理后的新头
        rest_new_head = self.reverseKGroup(curr, k)

        # 3. 翻转前 k 个节点
        # 注意:这里翻转时,要把第 k 个节点的 next 指向 rest_new_head
        new_head = self.reverseFirstK(head, k, rest_new_head)

        return new_head

    # 辅助函数:翻转前 k 个节点,并将尾部连接到 next_group
    def reverseFirstK(self, head: ListNode, k: int, next_group: ListNode) -> ListNode:
        prev = next_group
        curr = head
        for _ in range(k):
            next_node = curr.next
            curr.next = prev
            prev = curr
            curr = next_node
        return prev  # 返回新的头节点

📌 总结

方法优点缺点适用场景
迭代法空间复杂度 O(1),逻辑严谨,像工程师一样步步为营。代码量大,指针操作繁琐,容易写错。面试中要求空间复杂度严格为 O(1) 时。
递归法代码简洁,逻辑清晰,符合人类直觉。需要额外的栈空间,空间复杂度 O(N/k)。面试中允许使用额外空间,或者追求代码优雅时。

这道题是链表操作的试金石,建议先手写迭代法理解指针断连的细节,再尝试递归法体会分治思想。