算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
通俗易懂讲解"随机链表的复制"算法题目
一、题目是啥?一句话说清
给你一个链表,每个节点有一个随机指针,指向链表中的任意节点或空。你需要深拷贝这个链表,创建全新节点,并正确设置next和random指针。
示例:
- 输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
- 输出:复制后的链表,其中每个新节点的random指向新链表中的对应节点。
二、解题核心
使用哈希表来映射原节点到新节点。首先遍历原链表,创建所有新节点并存储映射关系。然后再次遍历原链表,根据映射关系设置新节点的next和random指针。
这就像先复制所有人的身份证(创建新节点),然后根据原关系网(原链表)来建立新关系网(新链表)。
三、关键在哪里?(3个核心点)
想理解并解决这道题,必须抓住以下三个关键点:
1. 哈希表的映射作用
- 是什么:使用哈希表存储原节点和新节点的对应关系,这样可以通过原节点快速找到新节点。
- 为什么重要:当设置random指针时,我们需要知道原节点对应的新节点是什么。哈希表提供了O(1)的查找效率,确保正确连接random指针。
2. 两次遍历链表
- 是什么:第一次遍历创建新节点并建立映射;第二次遍历设置指针。
- 为什么重要:第一次遍历确保所有新节点都被创建;第二次遍历利用映射正确设置random指针,因为random可能指向任何节点,需要通过哈希表找到对应的新节点。
3. 处理null指针的情况
- 是什么:如果原节点的random指针为null,新节点的random也应为null。
- 为什么重要:避免空指针异常,并确保复制准确。在访问哈希表时,需要检查原指针是否为null,否则会出错。
四、看图理解流程(通俗理解版本)
假设原链表为:A -> B -> C,其中A.random指向C,B.random指向A,C.random为null。
-
第一次遍历:创建新节点和映射
- 遍历原链表,对于每个节点,创建新节点,值相同。
- 将原节点A映射到新节点A',原节点B映射到新节点B',原节点C映射到新节点C'。
- 此时新节点还没有连接next和random。
-
第二次遍历:设置指针
- 再次遍历原链表,对于每个原节点,通过哈希表找到对应的新节点。
- 设置新节点的next:原节点A的next是B,所以新节点A'的next应该是B'(通过哈希表得到)。
- 设置新节点的random:原节点A的random是C,所以新节点A'的random应该是C'(通过哈希表得到)。同理,B的random是A,所以B'的random是A'。C的random为null,所以C'的random为null。
- 这样新链表就完整了。
五、C++ 代码实现(附详细注释)
cpp
#include <iostream>
#include <unordered_map>
using namespace std;
// 链表节点定义
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = nullptr;
random = nullptr;
}
};
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == nullptr) return nullptr;
unordered_map<Node*, Node*> map; // 哈希表:原节点 -> 新节点
// 第一次遍历:创建新节点并建立映射
Node* curr = head;
while (curr != nullptr) {
map[curr] = new Node(curr->val);
curr = curr->next;
}
// 第二次遍历:设置next和random指针
curr = head;
while (curr != nullptr) {
// 设置next指针:如果curr->next不为null,则映射到新节点;否则为null
map[curr]->next = curr->next ? map[curr->next] : nullptr;
// 设置random指针:如果curr->random不为null,则映射到新节点;否则为null
map[curr]->random = curr->random ? map[curr->random] : nullptr;
curr = curr->next;
}
return map[head];
}
};
// 测试代码(简单示例)
int main() {
// 构建示例链表:节点0:值7, random null; 节点1:值13, random指向0; 等等。
Node* head = new Node(7);
head->next = new Node(13);
head->next->next = new Node(11);
head->next->next->next = new Node(10);
head->next->next->next->next = new Node(1);
head->random = nullptr;
head->next->random = head; // 指向节点0
head->next->next->random = head->next->next->next->next; // 指向节点4
head->next->next->next->random = head->next->next; // 指向节点2
head->next->next->next->next->random = head; // 指向节点0
Solution solution;
Node* copyHead = solution.copyRandomList(head);
// 打印复制后的链表(简单打印值)
Node* curr = copyHead;
while (curr != nullptr) {
cout << "val: " << curr->val;
if (curr->random != nullptr) {
cout << ", random: " << curr->random->val;
} else {
cout << ", random: null";
}
cout << endl;
curr = curr->next;
}
return 0;
}
六、时间空间复杂度
- 时间复杂度:O(n),其中n是链表长度。我们遍历链表两次,每次都是O(n)。
- 空间复杂度:O(n),因为哈希表存储了n个键值对。
七、注意事项
- 处理null指针:在设置next和random指针时,必须检查原指针是否为null,否则访问map[null]会导致未定义行为。
- 哈希表覆盖:确保每个原节点只被映射一次,避免重复创建新节点。
- 深拷贝的含义:新链表节点必须是全新的,不能指向原链表的节点。哈希表方法自然保证了这一点。
- 内存管理:在实际应用中,需要注意内存释放,但本题只要求复制,不涉及释放原链表。
算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解