当前位置: 首页 > ops >正文

Java详解LeetCode 热题 100(32):LeetCode 138. 随机链表的复制

文章目录

    • 第1章:题目描述
      • 1.1 题目原文
      • 1.2 示例分析
        • 示例1:
        • 示例2:
        • 示例3:
      • 1.3 约束条件
      • 1.4 链表节点定义
    • 第2章:理解题目
      • 2.1 核心概念
        • 2.1.1 深拷贝 vs 浅拷贝
        • 2.1.2 随机指针的挑战
      • 2.2 问题可视化
      • 2.3 核心挑战
    • 第3章:解法一 - 哈希表映射法(两次遍历)
      • 3.1 算法思路
      • 3.2 算法步骤
      • 3.3 Java实现
      • 3.4 执行演示
      • 3.5 优缺点分析
    • 第4章:解法二 - 递归+哈希表法
      • 4.1 算法思路
      • 4.2 算法特点
      • 4.3 Java实现
      • 4.4 递归过程可视化
      • 4.5 处理循环引用
      • 4.6 优缺点分析
    • 第5章:解法三 - 原地复制法(O(1)空间)
      • 5.1 算法思路
      • 5.2 三步骤详解
        • 步骤1:插入新节点
        • 步骤2:设置random指针
        • 步骤3:分离链表
      • 5.3 Java实现
      • 5.4 详细执行演示
      • 5.5 关键技巧解析
        • 5.5.1 为什么要先插入所有节点?
        • 5.5.2 random指针的巧妙设置
      • 5.6 优缺点分析
    • 第6章:完整测试用例
      • 6.1 测试框架
      • 6.2 基础测试用例
      • 6.3 边界测试用例
    • 第7章:算法复杂度对比
      • 7.1 时间复杂度分析
      • 7.2 空间复杂度分析
      • 7.3 实际性能测试
      • 7.4 选择建议
    • 第8章:常见错误与调试技巧
      • 8.1 常见错误类型
        • 8.1.1 指针错误
        • 8.1.2 映射错误
        • 8.1.3 原地复制法特有错误
      • 8.2 调试技巧
        • 8.2.1 可视化调试
        • 8.2.2 单步调试示例
      • 8.3 测试驱动开发
    • 第9章:相关题目与拓展
      • 9.1 LeetCode相关题目
        • 9.1.1 链表复制类题目
        • 9.1.2 链表操作类题目
      • 9.2 算法模式扩展
        • 9.2.1 深拷贝模式
        • 9.2.2 原地算法模式
      • 9.3 实际应用场景
        • 9.3.1 对象序列化
        • 9.3.2 缓存系统
        • 9.3.3 状态管理
    • 第10章:学习建议与总结
      • 10.1 学习步骤建议
        • 10.1.1 初学者路径
        • 10.1.2 进阶学习
      • 10.2 面试要点
        • 10.2.1 常见面试问题
        • 10.2.2 回答技巧
      • 10.3 实际应用价值
        • 10.3.1 软件开发中的应用
        • 10.3.2 算法思维的培养
      • 10.4 总结

第1章:题目描述

1.1 题目原文

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

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

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

返回复制链表的头节点。

1.2 示例分析

示例1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

可视化表示:

原链表:
7 -> 13 -> 11 -> 10 -> 1
|    |     |     |    |
null 7     1     11   7复制链表:
7' -> 13' -> 11' -> 10' -> 1'
|     |      |      |     |
null  7'     1'     11'   7'
示例2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

可视化表示:

原链表:
1 -> 2
|    |
2    2复制链表:
1' -> 2'
|     |
2'    2'
示例3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

可视化表示:

原链表:
3 -> 3 -> 3
|    |    |
null 3    null复制链表:
3' -> 3' -> 3'
|     |     |
null  3'    null

1.3 约束条件

  • 0 <= n <= 1000
  • -10^4 <= Node.val <= 10^4
  • Node.randomnull 或指向链表中的节点

1.4 链表节点定义

// 链表节点定义
class Node {int val;Node next;Node random;public Node(int val) {this.val = val;this.next = null;this.random = null;}
}

第2章:理解题目

2.1 核心概念

2.1.1 深拷贝 vs 浅拷贝
  • 浅拷贝:只复制对象的引用,新旧对象共享内存
  • 深拷贝:创建全新的对象,包括所有嵌套对象
// 浅拷贝示例(错误做法)
Node shallowCopy = originalNode; // 只是复制引用// 深拷贝示例(正确做法)
Node deepCopy = new Node(originalNode.val);
deepCopy.next = copyRandomList(originalNode.next);
deepCopy.random = copyRandomList(originalNode.random);
2.1.2 随机指针的挑战

随机指针可能指向:

  1. 链表中的任意节点(包括自己)
  2. null
  3. 已经遍历过的节点
  4. 尚未遍历到的节点

2.2 问题可视化

让我们通过一个具体例子来理解问题:

原链表结构:
节点索引: 0    1    2    3    4
节点值:   7 -> 13 -> 11 -> 10 -> 1
random:   ↓    ↓     ↓     ↓    ↓null  0     4     2    0详细分析:
- 节点0(值7): random指向null
- 节点1(值13): random指向节点0(值7)
- 节点2(值11): random指向节点4(值1)
- 节点3(值10): random指向节点2(值11)
- 节点4(值1): random指向节点0(值7)

2.3 核心挑战

  1. 节点映射问题:如何建立原节点与新节点的对应关系?
  2. 前向引用问题:当前节点的random可能指向还未创建的节点
  3. 循环引用问题:节点可能相互引用,形成环
  4. 内存管理:确保不会内存泄漏

第3章:解法一 - 哈希表映射法(两次遍历)

3.1 算法思路

使用HashMap建立原节点与新节点的映射关系,分两次遍历:

  1. 第一次遍历:创建所有新节点,建立映射关系
  2. 第二次遍历:设置next和random指针

3.2 算法步骤

步骤1:第一次遍历 - 创建节点映射
for each node in original list:create new node with same valuemap[original_node] = new_node步骤2:第二次遍历 - 设置指针
for each node in original list:new_node = map[original_node]new_node.next = map[original_node.next]new_node.random = map[original_node.random]

3.3 Java实现

import java.util.*;public class Solution {/*** 解法一:哈希表映射法(两次遍历)* 时间复杂度:O(n)* 空间复杂度:O(n)*/public Node copyRandomList(Node head) {if (head == null) {return null;}// 第一次遍历:创建所有新节点并建立映射Map<Node, Node> nodeMap = new HashMap<>();Node current = head;while (current != null) {// 创建新节点Node newNode = new Node(current.val);// 建立原节点到新节点的映射nodeMap.put(current, newNode);current = current.next;}// 第二次遍历:设置next和random指针current = head;while (current != null) {Node newNode = nodeMap.get(current);// 设置next指针if (current.next != null) {newNode.next = nodeMap.get(current.next);}// 设置random指针if (current.random != null) {newNode.random = nodeMap.get(current.random);}current = current.next;}return nodeMap.get(head);}
}

3.4 执行演示

让我们用示例1来演示执行过程:

// 原链表:7->13->11->10->1
// random: null,0,4,2,0// 第一次遍历 - 创建节点映射
Map<Node, Node> nodeMap = new HashMap<>();// 遍历节点7
Node node7 = new Node(7);
nodeMap.put(original_7, node7);// 遍历节点13
Node node13 = new Node(13);
nodeMap.put(original_13, node13);// 遍历节点11
Node node11 = new Node(11);
nodeMap.put(original_11, node11);// 遍历节点10
Node node10 = new Node(10);
nodeMap.put(original_10, node10);// 遍历节点1
Node node1 = new Node(1);
nodeMap.put(original_1, node1);// 第二次遍历 - 设置指针
// 设置节点7
node7.next = nodeMap.get(original_13) = node13;
node7.random = null; // 原节点random为null// 设置节点13
node13.next = nodeMap.get(original_11) = node11;
node13.random = nodeMap.get(original_7) = node7;// 设置节点11
node11.next = nodeMap.get(original_10) = node10;
node11.random = nodeMap.get(original_1) = node1;// 设置节点10
node10.next = nodeMap.get(original_1) = node1;
node10.random = nodeMap.get(original_11) = node11;// 设置节点1
node1.next = null; // 链表末尾
node1.random = nodeMap.get(original_7) = node7;

3.5 优缺点分析

优点:

  • 思路清晰,易于理解
  • 代码简洁,不易出错
  • 时间复杂度最优O(n)

缺点:

  • 需要额外的O(n)空间存储映射
  • 需要遍历两次链表

第4章:解法二 - 递归+哈希表法

4.1 算法思路

使用递归的方式深度优先创建节点,同时用哈希表避免重复创建。

4.2 算法特点

  • 递归创建:遇到节点就递归创建其next和random指向的节点
  • 记忆化:用哈希表记录已创建的节点,避免重复创建
  • 延迟绑定:在递归返回时自然完成指针绑定

4.3 Java实现

import java.util.*;public class Solution {// 用于记录已创建的节点映射private Map<Node, Node> visited = new HashMap<>();/*** 解法二:递归+哈希表法* 时间复杂度:O(n)* 空间复杂度:O(n) - 包括递归栈和哈希表*/public Node copyRandomList(Node head) {return copyNode(head);}private Node copyNode(Node node) {// 基础情况:空节点if (node == null) {return null;}// 如果节点已经被复制过,直接返回if (visited.containsKey(node)) {return visited.get(node);}// 创建新节点Node newNode = new Node(node.val);// 先将映射关系存入map,防止循环引用导致无限递归visited.put(node, newNode);// 递归复制next和random指针newNode.next = copyNode(node.next);newNode.random = copyNode(node.random);return newNode;}
}

4.4 递归过程可视化

让我们用一个简单例子来理解递归过程:

原链表:A -> B -> null|    |B    A递归调用栈:
copyNode(A) {创建 A'visited[A] = A'A'.next = copyNode(B) {创建 B'visited[B] = B'B'.next = copyNode(null) = nullB'.random = copyNode(A) {// A已在visited中,直接返回A'return A'}return B'}A'.random = copyNode(B) {// B已在visited中,直接返回B'return B'}return A'
}

4.5 处理循环引用

递归法的一个重要优势是能够优雅地处理循环引用:

// 示例:节点自引用
Node selfRef = new Node(1);
selfRef.random = selfRef; // 指向自己// 递归处理过程
copyNode(selfRef) {创建 selfRef'visited[selfRef] = selfRef' // 关键:先存储映射selfRef'.next = copyNode(null) = nullselfRef'.random = copyNode(selfRef) {// selfRef已在visited中,返回selfRef'return selfRef'}return selfRef'
}

4.6 优缺点分析

优点:

  • 代码简洁优雅
  • 自然处理循环引用
  • 一次遍历完成

缺点:

  • 递归深度可能很大(最坏O(n))
  • 空间复杂度包括递归栈
  • 对于很长的链表可能栈溢出

第5章:解法三 - 原地复制法(O(1)空间)

5.1 算法思路

这是最巧妙的解法,通过在原链表中插入新节点来避免使用额外的哈希表空间。

核心思想:

  1. 在每个原节点后面插入对应的新节点
  2. 利用这种结构来设置新节点的random指针
  3. 分离原链表和新链表

5.2 三步骤详解

步骤1:插入新节点
原链表:A -> B -> C
插入后:A -> A' -> B -> B' -> C -> C'
步骤2:设置random指针
如果A.random = C,则A'.random = C.next = C'
步骤3:分离链表
恢复原链表:A -> B -> C
新链表:A' -> B' -> C'

5.3 Java实现

public class Solution {/*** 解法三:原地复制法(O(1)空间)* 时间复杂度:O(n)* 空间复杂度:O(1)*/public Node copyRandomList(Node head) {if (head == null) {return null;}// 步骤1:在每个节点后插入复制节点insertCopyNodes(head);// 步骤2:设置复制节点的random指针setRandomPointers(head);// 步骤3:分离原链表和复制链表return separateLists(head);}/*** 步骤1:在每个原节点后插入复制节点*/private void insertCopyNodes(Node head) {Node current = head;while (current != null) {// 创建复制节点Node copyNode = new Node(current.val);// 插入到当前节点后面copyNode.next = current.next;current.next = copyNode;// 移动到下一个原节点current = copyNode.next;}}/*** 步骤2:设置复制节点的random指针*/private void setRandomPointers(Node head) {Node current = head;while (current != null) {Node copyNode = current.next;// 如果原节点有random指针,设置复制节点的randomif (current.random != null) {copyNode.random = current.random.next;}// 移动到下一个原节点current = copyNode.next;}}/*** 步骤3:分离原链表和复制链表*/private Node separateLists(Node head) {Node copyHead = head.next;Node current = head;Node copyCurrent = copyHead;while (current != null) {// 恢复原链表current.next = copyCurrent.next;current = current.next;// 构建复制链表if (current != null) {copyCurrent.next = current.next;copyCurrent = copyCurrent.next;}}return copyHead;}
}

5.4 详细执行演示

让我们用示例来演示整个过程:

// 原链表:7->13->11,random: null,0,0// 步骤1:插入复制节点
// 原链表:7 -> 13 -> 11
// 插入后:7 -> 7' -> 13 -> 13' -> 11 -> 11'System.out.println("步骤1完成后的链表结构:");
// 7(random:null) -> 7'(random:null) -> 13(random:7) -> 13'(random:null) 
// -> 11(random:7) -> 11'(random:null)// 步骤2:设置random指针
// 对于节点7:random为null,所以7'.random = null
// 对于节点13:random指向7,所以13'.random = 7.next = 7'
// 对于节点11:random指向7,所以11'.random = 7.next = 7'System.out.println("步骤2完成后的random指针:");
// 7'(random:null), 13'(random:7'), 11'(random:7')// 步骤3:分离链表
// 原链表恢复:7 -> 13 -> 11
// 新链表:7' -> 13' -> 11'

5.5 关键技巧解析

5.5.1 为什么要先插入所有节点?
// 错误做法:边插入边设置random
Node copy = new Node(current.val);
copy.random = current.random.next; // 错误!random.next可能还不存在// 正确做法:先插入所有节点,再设置random
// 这样确保所有节点的next都指向对应的复制节点
5.5.2 random指针的巧妙设置
// 原节点的random指向某个节点X
// 复制节点的random应该指向X的复制节点
// 而X的复制节点就是X.next(因为我们在X后面插入了复制节点)
copyNode.random = current.random.next;

5.6 优缺点分析

优点:

  • 空间复杂度O(1)(不计算返回值)
  • 时间复杂度O(n)
  • 不需要额外的数据结构

缺点:

  • 算法较复杂,容易出错
  • 临时修改了原链表结构
  • 代码可读性相对较差

第6章:完整测试用例

6.1 测试框架

public class RandomListTest {/*** 创建测试链表的辅助方法*/public static Node createTestList(int[] values, int[] randomIndices) {if (values.length == 0) return null;// 创建所有节点Node[] nodes = new Node[values.length];for (int i = 0; i < values.length; i++) {nodes[i] = new Node(values[i]);}// 设置next指针for (int i = 0; i < values.length - 1; i++) {nodes[i].next = nodes[i + 1];}// 设置random指针for (int i = 0; i < values.length; i++) {if (randomIndices[i] != -1) {nodes[i].random = nodes[randomIndices[i]];}}return nodes[0];}/*** 验证复制结果的辅助方法*/public static boolean validateCopy(Node original, Node copy) {Map<Node, Integer> originalMap = new HashMap<>();Map<Node, Integer> copyMap = new HashMap<>();// 建立节点到索引的映射int index = 0;Node curr = original;while (curr != null) {originalMap.put(curr, index++);curr = curr.next;}index = 0;curr = copy;while (curr != null) {copyMap.put(curr, index++);curr = curr.next;}// 验证结构一致性Node origCurr = original;Node copyCurr = copy;while (origCurr != null && copyCurr != null) {// 验证值相同if (origCurr.val != copyCurr.val) {return false;}// 验证random指针指向的位置相同Integer origRandomIndex = origCurr.random == null ? null : originalMap.get(origCurr.random);Integer copyRandomIndex = copyCurr.random == null ? null : copyMap.get(copyCurr.random);if (!Objects.equals(origRandomIndex, copyRandomIndex)) {return false;}// 验证没有指向原链表if (copyMap.containsKey(origCurr) || originalMap.containsKey(copyCurr)) {return false;}origCurr = origCurr.next;copyCurr = copyCurr.next;}return origCurr == null && copyCurr == null;}
}

6.2 基础测试用例

public class TestCases {@Testpublic void testEmptyList() {Solution solution = new Solution();Node result = solution.copyRandomList(null);assertNull(result);}@Testpublic void testSingleNode() {// 测试单节点,random指向自己Node node = new Node(1);node.random = node;Solution solution = new Solution();Node result = solution.copyRandomList(node);assertNotNull(result);assertEquals(1, result.val);assertEquals(result, result.random);assertNotSame(node, result);}@Testpublic void testTwoNodes() {// 测试两个节点相互指向int[] values = {1, 2};int[] randomIndices = {1, 0};Node original = createTestList(values, randomIndices);Solution solution = new Solution();Node copy = solution.copyRandomList(original);assertTrue(validateCopy(original, copy));}@Testpublic void testComplexCase() {// 测试复杂情况:[[7,null],[13,0],[11,4],[10,2],[1,0]]int[] values = {7, 13, 11, 10, 1};int[] randomIndices = {-1, 0, 4, 2, 0}; // -1表示nullNode original = createTestList(values, randomIndices);Solution solution = new Solution();Node copy = solution.copyRandomList(original);assertTrue(validateCopy(original, copy));}@Testpublic void testAllRandomNull() {// 测试所有random都为null的情况int[] values = {1, 2, 3, 4, 5};int[] randomIndices = {-1, -1, -1, -1, -1};Node original = createTestList(values, randomIndices);Solution solution = new Solution();Node copy = solution.copyRandomList(original);assertTrue(validateCopy(original, copy));}@Testpublic void testAllRandomSelf() {// 测试所有random都指向自己int[] values = {1, 2, 3};int[] randomIndices = {0, 1, 2};Node original = createTestList(values, randomIndices);Solution solution = new Solution();Node copy = solution.copyRandomList(original);assertTrue(validateCopy(original, copy));}
}

6.3 边界测试用例

@Test
public void testLargeList() {// 测试大链表(1000个节点)int n = 1000;int[] values = new int[n];int[] randomIndices = new int[n];for (int i = 0; i < n; i++) {values[i] = i;randomIndices[i] = (i + 500) % n; // 随机指向}Node original = createTestList(values, randomIndices);Solution solution = new Solution();long startTime = System.currentTimeMillis();Node copy = solution.copyRandomList(original);long endTime = System.currentTimeMillis();assertTrue(validateCopy(original, copy));System.out.println("大链表测试耗时: " + (endTime - startTime) + "ms");
}@Test
public void testDuplicateValues() {// 测试重复值int[] values = {1, 1, 1, 1, 1};int[] randomIndices = {4, 3, 2, 1, 0};Node original = createTestList(values, randomIndices);Solution solution = new Solution();Node copy = solution.copyRandomList(original);assertTrue(validateCopy(original, copy));
}

第7章:算法复杂度对比

7.1 时间复杂度分析

解法时间复杂度遍历次数说明
哈希表法(两次遍历)O(n)2次第一次创建映射,第二次设置指针
递归+哈希表法O(n)1次递归遍历,每个节点访问一次
原地复制法O(n)3次插入、设置random、分离各一次

7.2 空间复杂度分析

解法空间复杂度额外空间说明
哈希表法(两次遍历)O(n)HashMap存储n个节点映射
递归+哈希表法O(n)HashMap + 递归栈映射+最坏O(n)递归深度
原地复制法O(1)只使用常数额外空间

7.3 实际性能测试

public class PerformanceTest {public static void performanceComparison() {int[] sizes = {100, 500, 1000, 5000};for (int size : sizes) {Node testList = generateRandomList(size);// 测试解法一long start1 = System.nanoTime();new Solution1().copyRandomList(testList);long time1 = System.nanoTime() - start1;// 测试解法二long start2 = System.nanoTime();new Solution2().copyRandomList(testList);long time2 = System.nanoTime() - start2;// 测试解法三long start3 = System.nanoTime();new Solution3().copyRandomList(testList);long time3 = System.nanoTime() - start3;System.out.printf("链表大小: %d\n", size);System.out.printf("哈希表法: %.2f ms\n", time1 / 1_000_000.0);System.out.printf("递归法: %.2f ms\n", time2 / 1_000_000.0);System.out.printf("原地法: %.2f ms\n", time3 / 1_000_000.0);System.out.println("---");}}
}

7.4 选择建议

推荐使用场景:

  1. 哈希表法(两次遍历)

    • 代码面试首选
    • 逻辑清晰,不易出错
    • 适合大多数实际应用
  2. 递归+哈希表法

    • 代码简洁优雅
    • 适合函数式编程风格
    • 注意递归深度限制
  3. 原地复制法

    • 内存受限环境
    • 追求极致空间效率
    • 有充足时间调试

第8章:常见错误与调试技巧

8.1 常见错误类型

8.1.1 指针错误
// 错误1:忘记处理null情况
public Node copyRandomList(Node head) {Map<Node, Node> map = new HashMap<>();// 错误:没有检查head是否为nullNode current = head;while (current != null) {// ...}
}// 正确做法
public Node copyRandomList(Node head) {if (head == null) return null; // 必须的null检查// ...
}
// 错误2:random指针可能为null
newNode.random = map.get(current.random); // 错误:如果current.random为null会出错// 正确做法
if (current.random != null) {newNode.random = map.get(current.random);
}
8.1.2 映射错误
// 错误3:使用值作为key(当有重复值时会出错)
Map<Integer, Node> map = new HashMap<>(); // 错误:应该用Node作为key
map.put(current.val, newNode);// 正确做法
Map<Node, Node> map = new HashMap<>();
map.put(current, newNode);
8.1.3 原地复制法特有错误
// 错误4:分离链表时指针处理错误
while (current != null) {current.next = current.next.next; // 错误:可能导致空指针current = current.next;
}// 正确做法
while (current != null) {Node copyNode = current.next;current.next = copyNode.next;current = current.next;if (current != null) {copyNode.next = current.next;}
}

8.2 调试技巧

8.2.1 可视化调试
public class DebugHelper {/*** 打印链表结构(包括random指针)*/public static void printList(Node head, String name) {System.out.println("=== " + name + " ===");// 建立节点到索引的映射Map<Node, Integer> nodeIndex = new HashMap<>();Node current = head;int index = 0;while (current != null) {nodeIndex.put(current, index++);current = current.next;}// 打印每个节点的信息current = head;index = 0;while (current != null) {Integer randomIndex = current.random == null ? null : nodeIndex.get(current.random);System.out.printf("节点%d: val=%d, random->%s\n", index, current.val, randomIndex == null ? "null" : "节点" + randomIndex);current = current.next;index++;}System.out.println();}/*** 验证链表完整性*/public static boolean validateIntegrity(Node head) {Set<Node> visited = new HashSet<>();Node current = head;while (current != null) {if (visited.contains(current)) {System.out.println("检测到next指针环!");return false;}visited.add(current);current = current.next;}return true;}
}
8.2.2 单步调试示例
public Node copyRandomListWithDebug(Node head) {if (head == null) return null;Map<Node, Node> map = new HashMap<>();Node current = head;System.out.println("开始第一次遍历...");while (current != null) {Node newNode = new Node(current.val);map.put(current, newNode);System.out.printf("创建节点: val=%d, 映射大小=%d\n", current.val, map.size());current = current.next;}System.out.println("开始第二次遍历...");current = head;while (current != null) {Node newNode = map.get(current);if (current.next != null) {newNode.next = map.get(current.next);System.out.printf("设置next: %d -> %d\n", newNode.val, newNode.next.val);}if (current.random != null) {newNode.random = map.get(current.random);System.out.printf("设置random: %d -> %d\n", newNode.val, newNode.random.val);}current = current.next;}return map.get(head);
}

8.3 测试驱动开发

// 先写测试,再写实现
@Test
public void testSpecificCase() {// 构造特定的测试用例Node node1 = new Node(1);Node node2 = new Node(2);node1.next = node2;node1.random = node2;node2.random = node1;// 调试输出DebugHelper.printList(node1, "原链表");Solution solution = new Solution();Node result = solution.copyRandomList(node1);// 调试输出DebugHelper.printList(result, "复制链表");// 验证结果assertTrue(validateCopy(node1, result));
}

第9章:相关题目与拓展

9.1 LeetCode相关题目

9.1.1 链表复制类题目
  • LeetCode 133. 克隆图:类似的深拷贝问题,但是图结构
  • LeetCode 1485. 克隆含随机指针的二叉树:树结构的随机指针复制
9.1.2 链表操作类题目
  • LeetCode 206. 反转链表:基础链表操作
  • LeetCode 92. 反转链表II:部分反转
  • LeetCode 25. K个一组翻转链表:分组操作
  • LeetCode 143. 重排链表:复杂链表重构

9.2 算法模式扩展

9.2.1 深拷贝模式
/*** 通用深拷贝接口*/
public interface DeepCopyable<T> {T deepCopy();
}/*** 图的深拷贝*/
public class GraphNode implements DeepCopyable<GraphNode> {int val;List<GraphNode> neighbors;public GraphNode deepCopy() {Map<GraphNode, GraphNode> visited = new HashMap<>();return dfs(this, visited);}private GraphNode dfs(GraphNode node, Map<GraphNode, GraphNode> visited) {if (node == null) return null;if (visited.containsKey(node)) return visited.get(node);GraphNode copy = new GraphNode(node.val);visited.put(node, copy);for (GraphNode neighbor : node.neighbors) {copy.neighbors.add(dfs(neighbor, visited));}return copy;}
}
9.2.2 原地算法模式
/*** 原地算法的通用思路:* 1. 利用现有空间存储额外信息* 2. 分阶段处理* 3. 最后恢复原始状态*/// 示例:原地合并两个有序数组
public void merge(int[] nums1, int m, int[] nums2, int n) {// 从后往前合并,避免覆盖int i = m - 1, j = n - 1, k = m + n - 1;while (i >= 0 && j >= 0) {nums1[k--] = nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];}while (j >= 0) {nums1[k--] = nums2[j--];}
}

9.3 实际应用场景

9.3.1 对象序列化
/*** 对象深拷贝在序列化中的应用*/
public class SerializationExample {public static <T> T deepCopy(T original) {try {ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(original);ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (T) ois.readObject();} catch (Exception e) {throw new RuntimeException("深拷贝失败", e);}}
}
9.3.2 缓存系统
/*** 缓存系统中的对象复制*/
public class CacheSystem {private Map<String, Node> cache = new HashMap<>();public Node get(String key) {Node cached = cache.get(key);if (cached == null) return null;// 返回深拷贝,避免外部修改影响缓存return copyRandomList(cached);}public void put(String key, Node value) {// 存储深拷贝,避免外部修改影响缓存cache.put(key, copyRandomList(value));}
}
9.3.3 状态管理
/*** 游戏状态的快照和回滚*/
public class GameState {private Node gameData;private Stack<Node> snapshots = new Stack<>();public void saveSnapshot() {// 保存当前状态的深拷贝snapshots.push(copyRandomList(gameData));}public void rollback() {if (!snapshots.isEmpty()) {gameData = snapshots.pop();}}
}

第10章:学习建议与总结

10.1 学习步骤建议

10.1.1 初学者路径
  1. 理解基础概念

    • 深拷贝 vs 浅拷贝
    • 链表的基本操作
    • 指针和引用的区别
  2. 掌握第一种解法

    • 从哈希表法开始
    • 理解映射的概念
    • 练习调试技巧
  3. 逐步进阶

    • 尝试递归解法
    • 挑战原地算法
    • 对比不同解法
10.1.2 进阶学习
  1. 算法优化

    • 分析时间空间复杂度
    • 考虑边界情况
    • 代码重构和优化
  2. 模式识别

    • 识别深拷贝模式
    • 掌握原地算法技巧
    • 理解递归的应用
  3. 实际应用

    • 在项目中应用
    • 解决实际问题
    • 扩展到其他数据结构

10.2 面试要点

10.2.1 常见面试问题
  1. 基础问题

    • “请解释什么是深拷贝?”
    • “为什么不能简单地复制指针?”
    • “如何处理循环引用?”
  2. 算法问题

    • “能否用O(1)空间解决?”
    • “递归解法的优缺点是什么?”
    • “如何优化时间复杂度?”
  3. 扩展问题

    • “如果是图结构怎么办?”
    • “如何处理更复杂的数据结构?”
    • “在实际项目中如何应用?”
10.2.2 回答技巧
  1. 思路清晰

    面试官:请实现随机链表的深拷贝回答框架:
    1. 确认题目理解(什么是深拷贝,random指针的含义)
    2. 分析核心难点(节点映射,前向引用)
    3. 提出解决方案(哈希表映射)
    4. 编写代码实现
    5. 分析复杂度
    6. 讨论优化方案
    
  2. 代码规范

    • 变量命名清晰
    • 添加必要注释
    • 处理边界情况
    • 代码结构清晰

10.3 实际应用价值

10.3.1 软件开发中的应用
  1. 对象克隆:Java中的Cloneable接口实现
  2. 状态管理:游戏、编辑器的撤销重做功能
  3. 缓存系统:防止缓存对象被意外修改
  4. 并发编程:线程安全的对象复制
10.3.2 算法思维的培养
  1. 分治思想:将复杂问题分解为子问题
  2. 空间换时间:哈希表映射的经典应用
  3. 原地算法:在有限空间内解决问题
  4. 递归思维:自然处理复杂的引用关系

10.4 总结

随机链表的复制是一道经典的链表操作题目,它不仅考查了基本的链表操作能力,更重要的是培养了以下几个方面的能力:

  1. 问题分析能力:如何将复杂问题分解为可解决的子问题
  2. 数据结构应用:哈希表在建立映射关系中的巧妙应用
  3. 算法优化思维:从O(n)空间到O(1)空间的优化过程
  4. 代码实现能力:处理复杂指针关系的编程技巧

通过深入学习这道题目,我们不仅掌握了三种不同的解法,更重要的是理解了深拷贝的本质,学会了在面对复杂数据结构时如何系统性地分析和解决问题。

这些技能在实际的软件开发中有着广泛的应用,无论是系统设计、算法优化,还是日常的编程工作,都能从中受益。

http://www.xdnf.cn/news/13861.html

相关文章:

  • Linux常用命令加强版替代品
  • 探索弹性弦行为:从绘图到问题解决-AI云计算数值分析和代码验证
  • 永不休眠:Linux 守护进程的工作原理
  • visual studio小番茄插件某些快捷键失效
  • 1万美元iO bounty破解之旅
  • android aosp源码下编码时避免引用aidl文件飘红不自动提示的方法
  • 神经网络压缩
  • 本地windows搭建kafka
  • 青少年编程与数学 01-011 系统软件简介 17 Hadoop大数据处理框架
  • NLP进化史:从规则模板到思维链推理,七次范式革命全解析
  • Vue3 + TypeScript + Element Plus 开启边框 > 调整列宽(拖动表头)> 保存列宽(本地存储)> 加载列宽(读取本地数据)
  • 基于物品的协同过滤推荐算法实现(Java电商平台)
  • 基于用户的协同过滤推荐算法实现(Java电商平台)
  • 微服务--Gateway网关
  • 开源组件hive页面安全问题
  • 【IEEE/EI/Scopus检索】2025年第六届模式识别与数据挖掘国际会议 (PRDM 2025)
  • Python爬虫进阶:气象数据爬取中的多线程优化与异常处理技巧
  • Java并发进阶系列:深度讨论高并发跳表数据结构ConcurrentSkipListMap的源代码实现(上)
  • python类成员概要
  • 当空间与数据联动,会展中心如何打造智慧运营新范式?
  • 当机床开始“思考”,传统“制造”到“智造”升级路上的法律暗礁
  • 驱动开发前传及led驱动(s5pv210)
  • 深度学习——基于PyTorch的MNIST手写数字识别详解
  • Python数据结构与算法(6.1)——树
  • 使用 Spring Boot 和 dynamic-datasource 实现多数据源集成
  • 从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(1)
  • 深入解析 SNMP Walk 的响应机制
  • 智能疲劳驾驶检测系统算法设计与研究
  • 山东大学软件学院项目实训:基于大模型的模拟面试系统项目总结(八)
  • 微信小程序生成小程序码缓存删除