Leetcode 链表 3

题号直达: 25 K 个一组翻转链表 | 138 随机链表的复制

LeetCode 25. K 个一组翻转链表 ↑

题目

给你链表的头节点 head,每 (k) 个节点一组进行翻转,请你返回修改后的链表。

(k) 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 (k) 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

提示

  • 链表中的节点数目为 (n)
  • (1 ≤ k ≤ n ≤ 5000)
  • (0 ≤ Node.val ≤ 1000)

进阶:你可以设计一个只用 (O(1)) 额外内存空间的算法解决此问题吗?

代码(Python)

class Solution(object):
    def reverseKGroup(self, head, k):
        """
        :type head: Optional[ListNode]
        :type k: int
        :rtype: Optional[ListNode]
        """
        def reverseList_correct(head, tail):
            """
            反转从 head 到 tail 的链表(包括 tail)
            返回 (newHead, newTail)
            """
            prev = tail.next  # 保存 tail 后面的节点
            curr = head
            last = tail.next  # 记录反转后的尾节点应该指向的位置
            
            while prev != tail:
                next_temp = curr.next
                curr.next = prev
                prev = curr
                curr = next_temp
            
            return tail, head  # tail 变成新的头,head 变成新的尾

        hair = ListNode(0)
        hair.next = head
        pre = hair
        
        while True:
            # 找到当前组的尾节点
            tail = pre
            for i in range(k):
                tail = tail.next
                if not tail:
                    return hair.next
            
            # 保存下一组的头节点
            next_group_head = tail.next
            
            # 反转当前组
            newHead, newTail = reverseList_correct(head, tail)
            
            # 连接回原链表
            pre.next = newHead
            newTail.next = next_group_head
            
            # 更新指针,准备下一组
            pre = newTail
            head = next_group_head

思路解析

  • 整体流程:用哑节点 hair 作为哨兵,pre 指向「当前待翻转组」的前驱。每次循环:先通过 for i in range(k) 找到本组的尾节点 tail;若不足 (k) 个(tail 为 None),直接返回;否则反转 [head, tail] 这一段,再把反转后的子链表接回 pre 和下一组之间。
  • 反转子链表:reverseList_correct(head, tail) 将 head 到 tail 的节点原地反转。用 prev = tail.next 作为反转后尾节点的 next,迭代地让每个节点的 next 指向前驱,最后返回 (tail, head) 作为新的头、尾。
  • 连接与更新:反转后 pre.next = newHead,newTail.next = next_group_head;然后将 pre 移到 newTail,head 移到 next_group_head,进入下一组。
  • 复杂度:每个节点被访问常数次,时间复杂度 (O(n));只使用常数个指针,空间复杂度 (O(1)),满足进阶要求。

LeetCode 138. 随机链表的复制 ↑

题目

给你一个长度为 (n) 的链表,每个节点包含一个额外增加的随机指针 random,该指针可以指向链表中的任何节点或空节点。

构造这个链表的深拷贝。深拷贝应该由恰好 (n) 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random → Y。那么在复制链表中对应的两个节点 x 和 y,同样有 x.random → y。

返回复制链表的头节点。

提示

  • (0 ≤ n ≤ 1000)
  • (-10^4 ≤ Node.val ≤ 10^4)
  • Node.random 为 null 或指向链表中的节点

代码(Python)解法一:递归 + 哈希表

class Solution(object):
    def __init__(self):
        self.cachedNode = {}
    
    def copyRandomList(self, head):
        """
        :type head: Node
        :rtype: Node
        """
        if head is None:
            return None
        
        if head not in self.cachedNode:
            # 创建新节点
            headNew = Node(head.val)
            self.cachedNode[head] = headNew
            
            # 递归复制 next 和 random 指针
            headNew.next = self.copyRandomList(head.next)
            headNew.random = self.copyRandomList(head.random)
        
        return self.cachedNode[head]

思路解析(解法一)

  • 核心思想:递归 + 哈希表(cachedNode),将原节点 → 新节点一一对应。
  • 先创建节点,再连接指针:进入 if head not in self.cachedNode 后立刻 Node(head.val) 并写入缓存,再去递归填 next / random,避免在「新节点尚未存在」时就引用它。
  • 先存入缓存,再递归:把 cachedNode[head] = headNew 放在递归之前,这样当 random 或 next 指回已访问过的原节点时,后续递归会直接 return self.cachedNode[head],不会无限递归。
  • next 与 random 分别递归:两条边独立复制,逻辑对称;head.random 为 None 时 copyRandomList(None) 返回 None,合法。
  • 利用缓存返回已建节点:保证每个原节点只对应一个新节点,满足深拷贝语义。

复杂度(解法一)

  • 时间复杂度 (O(n)):每个原节点至多创建一次、每条边(next / random)在递归树中对应常数次访问。
  • 空间复杂度 (O(n)):哈希表存 (n) 个映射,递归栈深度 (O(n))。

示例:逐步走读代码(解法一)

设原链表为两个节点(仅示意结构):

原节点 val next random
A 1 B B
B 2 None A

即:A → B,A.random → B,B.random → A(存在环状引用,最能体现「先缓存再递归」的必要性)。

记新节点为 A'、B',初始 cachedNode = {}。

  1. copyRandomList(A)

    • A 不在缓存 → 创建 A'(值为 1),cachedNode[A] = A'。
    • 执行 headNew.next = copyRandomList(B)(先不填 random)。
  2. copyRandomList(B)

    • B 不在缓存 → 创建 B'(值为 2),cachedNode[B] = B'。
    • headNew.next = copyRandomList(None) → 返回 None,故 B'.next = None。
    • headNew.random = copyRandomList(A):此时 A 已在缓存,不再进入 if 内创建逻辑,直接 return cachedNode[A] = A',故 B'.random → A'。
    • 返回 B'。
  3. 回到 copyRandomList(A) 中未完成的赋值:

    • A'.next = B'(上一步返回值)。
    • headNew.random = copyRandomList(B):B 已在缓存,直接 return B',故 A'.random → B'。
  4. 返回 A',即为复制链表的头。

结果核对:复制链为 A' → B',A'.random → B',B'.random → A',与原件结构一致,且所有指针只指向新节点。

若不做「先缓存再递归」:例如在创建 A' 后立刻 copyRandomList(B),而在处理 B 的 random 时又需要 A',若没有提前 cachedNode[A]=A',要么拿不到 A',要么会重复创建 A'',深拷贝不成立;若 random 先于 next 递归且未缓存,也可能在填 A'.random 时再次进入对 A 的复制,造成无限递归。先占位、再递归 同时解决了「环」与「重复创建」两类问题。

代码(Python)解法二:交错插入 + 拆链((O(1)) 额外空间)

class Solution(object):
    
    def copyRandomList(self, head):
        """
        :type head: Node
        :rtype: Node
        """
        if not head:
            return None

            
        p = head
        while p != None:
            p_new = Node(p.val)
            p_new.next = p.next
            p.next = p_new
            p = p.next.next

        # return head
        
        
        odd = head    # Original
        even = head.next    # New

        while odd != None:
            if odd.random == None:
                even.random = None
            else:
                even.random = odd.random.next
            odd = odd.next.next
            if odd == None:
                break
            even = even.next.next

        odd = head
        even = head.next
        ans = head.next

        while odd != None:
            odd.next = even.next
            if even.next != None:
                even.next = even.next.next
            odd = odd.next
            even = even.next

        return ans

思路解析(解法二)

  • 第一阶段(交错插入):对每个原节点 p,在其后插入拷贝 p_new,使结构变为 … → p → p_new → p_old_next → …。这样原节点 p 的下一个节点就是自己的拷贝,无需哈希表即可建立对应关系。
  • 第二阶段(复制 random):odd 遍历原节点(下标 0,2,4…),even 为紧跟其后的新节点。若 odd.random 指向某原节点 R,则拷贝的 random 应为 R 后面的新节点,即 even.random = odd.random.next;odd.random 为 None 时新节点 random 也为 None。
  • 第三阶段(拆链):恢复原链表的 next,同时把拷贝链连成独立链表:odd.next = even.next 跳过拷贝节点;even.next = even.next.next 把拷贝节点串起来。ans = head.next 为拷贝链头,最后返回 ans。
  • 复杂度:时间 (O(n)),额外空间 (O(1))(不计输出链表),满足进阶要求。

posted @ 2026-10-11 17:49  Phier  阅读(9)  评论(0)    收藏  举报