【力扣-138. 随机链表的复制 ✨】Python笔记

拒绝"浅拷贝"陷阱:图解随机链表的深拷贝

摘要 :本文详解LeetCode 138题"随机链表的复制"。通过巧妙的"拼接-赋值-拆分"三步走策略,在O(空间复杂度下实现链表深拷贝,彻底解决random指针指向难题,带你领略链表操作的思维之美。


📚 核心知识点:深拷贝与"时空穿梭"技巧

在处理链表复制问题时,我们通常会遇到一个棘手的难题:random指针。普通的链表复制只需要复制valnext,但random指针可能指向链表中的任意节点(甚至是null)。如果我们直接复制,新节点的random就会指向旧节点,这就不符合"深拷贝"的要求了。

为了解决这个问题,通常有两种思路:

  1. 哈希表法(空间换时间) :用字典记录旧节点 -> 新节点的映射关系。但这需要O(N)的额外空间。
  2. 拼接拆分法(原地操作):这就是我们要讲的重点!通过巧妙的"穿插"技巧,将空间复杂度降为O(1)

核心思想

既然新节点找不到它对应的random新节点,那我们就把新节点紧紧贴在旧节点身边!这样,通过旧节点的random,就能瞬间找到新节点的random


📝 题目解析:LeetCode 138. 随机链表的复制

题目描述

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

构造这个链表的深拷贝 。深拷贝应该正好由n全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的next指针和random指针也都应指向复制链表中的新节点。

简单来说:你要造一个一模一样的克隆链表,但里面的零件全是新的,不能混用旧的。


💡 解题思路:三步走的"魔术"

我们要通过三个步骤,把旧链表变成新链表:

第一步:拼接(插队)

在每个旧节点后面,立刻插入一个克隆的新节点。

  • 旧链表:A -> B -> C
  • 拼接后:A -> A' -> B -> B' -> C -> C'

第二步:赋值(连Random)

利用"近水楼台先得月"的优势,设置新节点的random

  • 如果A.random指向C,那么A'.random就应该指向C'
  • 因为C'就在C的旁边(C.next),所以我们可以轻松找到它。

第三步:拆分(分家)

把纠缠在一起的链表拆开,恢复旧链表,提取新链表。

  • 旧链表恢复为:A -> B -> C
  • 新链表提取为:A' -> B' -> C'

💻 代码实现(Python3)
python 复制代码
"""  
# Definition for a Node.  
class Node:  
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):  
        self.val = int(x)  
        self.next = next  
        self.random = random  
"""  
class Solution:  
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':  
        if not head:  
            return None  

        # ===================== 步骤1:拼接链表,插入新节点 =====================  
        # 目的:A -> B -> C  变为  A -> A' -> B -> B' -> C -> C'  
        cur = head  
        while cur:  
            # 1. 创建新节点,值和当前原节点相同  
            new_node = Node(cur.val)  
            # 2. 新节点的next指向原节点的next(即B)  
            new_node.next = cur.next  
            # 3. 原节点的next指向新节点(即A'),完成插入  
            cur.next = new_node  
            # 4. 移动到下一个原节点(跳过刚插入的新节点,即从A跳到B)  
            cur = new_node.next  

        # ===================== 步骤2:给新节点的random指针赋值 =====================  
        # 原理:如果 A.random -> C,那么 A'.random 应该 -> C' (即 C.next)  
        cur = head  
        while cur:  
            # 当前原节点对应的新节点(就在原节点旁边)  
            new_node = cur.next  
            # 如果原节点的random不为空  
            if cur.random:  
                # 新节点的random = 原节点random的next(对应的克隆节点)  
                new_node.random = cur.random.next  
            else:  
                new_node.random = None  
            # 移动到下一个原节点  
            cur = new_node.next  

        # ===================== 步骤3:拆分链表,恢复原链表 + 提取新链表 =====================  
        cur = head  
        # 新链表的头节点(原头节点的next)  
        new_head = head.next  
        while cur:  
            new_node = cur.next  

            # 1. 恢复原节点的next指针(跳过新节点,连向后一个原节点)  
            cur.next = new_node.next  

            # 2. 给新节点的next指针赋值(如果有下一个新节点的话)  
            # 注意:new_node.next 此时指向的是"下一个原节点",我们需要跳到"下一个新节点"  
            if new_node.next:  
                new_node.next = new_node.next.next  
            else:  
                new_node.next = None  

            # 3. 移动到下一个原节点  
            cur = cur.next  

        # 返回新链表的头节点  
        return new_head  

🚀 递归写法:大道至简的"回溯法"

如果你觉得上面的"拼接拆分"像是在变魔术,容易绕晕,那么递归 + 哈希表的方法可能更符合直觉。

核心逻辑

递归的本质是"把大问题拆成小问题"。复制一个节点,取决于复制它的nextrandom

为了防止无限循环(因为random可能指回前面的节点),我们需要一个"备忘录"(哈希表)来记录旧节点 -> 新节点的映射。

python 复制代码
class Solution:  
    # 定义一个哈希表作为缓存,key是旧节点,value是新节点  
    cached_node = {}  

    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':  
        if not head:  
            return None  

        # 如果这个节点还没被克隆过  
        if head not in self.cached_node:  
            # 1. 先创建新节点(只赋值val,next和random先空着)  
            new_node = Node(head.val)  
            # 2. 存入哈希表,标记"这个旧节点我已经处理了"  
            self.cached_node[head] = new_node  
            # 3. 递归地去克隆它的 next 和 random  
            # 这里的精妙之处在于:递归调用会自动处理"是否已存在"的检查  
            new_node.next = self.copyRandomList(head.next)  
            new_node.random = self.copyRandomList(head.random)  

        # 如果已经克隆过了,直接返回哈希表里的新节点  
        return self.cached_node[head]  

递归法总结

  • 优点:逻辑非常清晰,代码短,不需要修改原链表结构。
  • 缺点:需要O(N)的额外空间来存哈希表,且递归过深可能导致栈溢出(虽然Python通常能处理几千层)。

📌 总结
  • 迭代法(拼接拆分):是空间复杂度的王者O(1),适合对空间要求严格的场景,面试时写出来说明你对链表操作非常熟练。
  • 递归法(哈希表):是思维清晰度的王者,代码简洁不易出错,适合快速解题。

希望这篇笔记能帮你彻底搞懂随机链表!下次见~

相关推荐
王忘杰2 小时前
0基础CUDA炼丹、增加断点保存,从零开始训练自己的AI大模型 87owo/EasyGPT Python CUDA
开发语言·人工智能·python
数据知道2 小时前
claw-code 源码详细分析:`reference_data` JSON 快照——大型移植里「对照底稿」该怎么治理与演进?
linux·python·ubuntu·json·claude code
好家伙VCC2 小时前
**发散创新:基于以太坊侧链的高性能去中心化应用部署实战**在区块链生态中,*
java·python·去中心化·区块链
瞭望清晨2 小时前
Python多进程使用场景
开发语言·python
py有趣2 小时前
力扣热门100题之最小覆盖子串
算法·leetcode
汀、人工智能2 小时前
04 - 控制流:if/for/while
数据结构·算法·链表·数据库架构··if/for/while
春蕾夏荷_7282977252 小时前
vscode 创建第一个python程序
vscode·python
qq_254674412 小时前
pysnmp 最新版本
python
HuaCode2 小时前
Openclaw一键安装部署(2026年4月最新)
git·python·nodejs·openclaw·api token