LeetCode 面试经典 150_链表_随机链表的复制(59_138_C++_中等)
-
- 题目描述:
- 输入输出样例:
- 题解:
-
- 解题思路:
-
- 思路一(哈希表):
- [思路二(迭代 + 节点拆分):](#思路二(迭代 + 节点拆分):)
- 代码实现
-
- 代码实现(思路一(哈希表)):
- [代码实现(思路二(迭代 + 节点拆分)):](#代码实现(思路二(迭代 + 节点拆分)):)
- 以思路二为例完成代码调试
题目描述:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝 。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
输入输出样例:
示例 1:

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

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

输入 :head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
提示:
0 <= n <= 1000
-104 <= Node.val <= 104
Node.random 为 null 或指向链表中的节点。
题解:
解题思路:
思路一(哈希表):
1、我们可以直接将原链表和复制链表中对应的结点存储在哈希表 中,这样我们就可以直接通过原地址的 next 和 random 来更新复制链表的指向。
例:

2、具体思路如下:
① 创建一个哈希表用于存储两个链表中每个结点地址的对应关系。
② 遍历原链表的同时创建复制链表的 val ,并使用哈希表记录原链表结点地址和复制链表地址的对应关系。
③ 用过哈希表更新复制链表的 next 和 random指向。
3、复杂度分析
① 时间复杂度:O(N),N代表原链表所含结点的数量。创建复制结点的时间复杂度为N,更新结点的指向的时间消耗为N,所以时间复杂度为O(N)。
② 空间复杂度:O(N),,使用了一个额外的哈希表来存储结点地址的对应关系(除存储答案所需空间)。
思路二(迭代 + 节点拆分):
1、我们可以将复制结点创建出来之后直接插入在原结点后边。这样我们在更新random的指向时就可以直接通过原结点random的指向来更新复制结点random的指向。
例:

2、具体思路如下:
① 在每个原结点后边创建一个复制结点并插入,将复制结点的random设置为nullptr。
② 遍历创建后的链表,更新复制结点的random的指向。
③ 将复制结点查下来,将原链表进行复原。
3、复杂度分析
① 时间复杂度:O(N),N代表原链表所含结点的数量。创建复制结点的时间复杂度为N,更新结点的指向的时间消耗为N,所以时间复杂度为O(N)。
② 空间复杂度:O(1),除存储答案所需空间。
代码实现
代码实现(思路一(哈希表)):
cpp
class Solution1 {
public:
// 复制带有随机指针的链表
Node* copyRandomList(Node* head) {
// 创建一个unordered_map来存储原链表节点和新链表节点的映射关系
unordered_map<Node*, Node*> map;
// 第一步:遍历原链表,为每个节点创建一个新的节点
Node *curNode = head;
while (curNode != nullptr) {
// 为当前节点创建一个新的节点,并将原节点和新节点的映射添加到map中
Node *newNode = new Node(curNode->val);
map[curNode] = newNode;
// 移动到下一个节点
curNode = curNode->next;
}
// 第二步:重新遍历原链表,设置新节点的 next 和 random 指针
curNode = head;
while (curNode != nullptr) {
// 为新节点设置 next 和 random 指针,map[curNode->next] 和 map[curNode->random] 通过原节点找到对应的新节点
map[curNode]->next = map[curNode->next]; // 设置新节点的 next 指针
map[curNode]->random = map[curNode->random]; // 设置新节点的 random 指针
// 移动到下一个节点
curNode = curNode->next;
}
// 返回新链表的头节点
return map[head];
}
};
代码实现(思路二(迭代 + 节点拆分)):
cpp
class Solution2 {
public:
Node* copyRandomList(Node* head) {
if (head == nullptr) {
return nullptr;
}
Node* head1 = head;
// 创建复制的结点插入原链表
while (head1 != nullptr) {
Node* newNode = new Node(head1->val);
newNode->next = head1->next;
head1->next = newNode;
head1 = head1->next->next;
}
//更新head1指向首节点
head1 = head;
// 修改新创建结点random的指向
while (head1 != nullptr) {
if (head1->random != nullptr) {
head1->next->random = head1->random->next;
}
head1 = head1->next->next;
}
//将链表一和链表二进行拆分,不使用新的头节点
head1 = head;
Node* head2 = head->next;
Node* tmp_head2 = head->next;
while (head2->next != nullptr) {
head1->next = head1->next->next;
head2->next = head1->next->next;
head1 = head1->next;
head2 = head2->next;
}
head1->next = nullptr;
return tmp_head2;
//将链表一和链表二进行拆分
//使用一个新的头节点来链接拆分的链表二
// Node *dummyNode=new Node(0);
// head1=head;
// Node *head2=dummyNode;
// while(head1!=nullptr){
// head2->next=head1->next;
// head2=head1->next;
// head1->next=head2->next;
// head1=head2->next;
// }
// head2=dummyNode->next;
// delete dummyNode;
// return head2;
}
};
以思路二为例完成代码调试
cpp
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
class Node{
public:
int val;
Node *next;
Node *random;
Node(int _val){
val=_val;
next=NULL;
random=NULL;
}
};
/*创建随机链表*/
Node *createNode(vector<vector<int>> &arr){
Node *head=nullptr,*tail=nullptr;
int n=0;
//id_adress保存每个结点的序号和id,使其对应
unordered_map<int,Node *> id_adress;
//采用尾插法先创建一个单链表,先使得random=nullptr
for(const auto &i:arr){
if(head==nullptr){
head=tail=new Node(i[0]);
}else{
tail->next=new Node(i[0]);
tail=tail->next;
}
id_adress[n]=tail;
++n;
}
//将random通过adress_id进行赋值,-1代表nullptr
tail=head;
for(const auto &i:arr){
if(i[1]==-1){
tail=tail->next;
continue;
}
tail->random=id_adress[i[1]];
tail=tail->next;
}
return head;
}
class Solution1 {
public:
// 复制带有随机指针的链表
Node* copyRandomList(Node* head) {
// 创建一个unordered_map来存储原链表节点和新链表节点的映射关系
unordered_map<Node*, Node*> map;
// 第一步:遍历原链表,为每个节点创建一个新的节点
Node *curNode = head;
while (curNode != nullptr) {
// 为当前节点创建一个新的节点,并将原节点和新节点的映射添加到map中
Node *newNode = new Node(curNode->val);
map[curNode] = newNode;
// 移动到下一个节点
curNode = curNode->next;
}
// 第二步:重新遍历原链表,设置新节点的 next 和 random 指针
curNode = head;
while (curNode != nullptr) {
// 为新节点设置 next 和 random 指针,map[curNode->next] 和 map[curNode->random] 通过原节点找到对应的新节点
map[curNode]->next = map[curNode->next]; // 设置新节点的 next 指针
map[curNode]->random = map[curNode->random]; // 设置新节点的 random 指针
// 移动到下一个节点
curNode = curNode->next;
}
// 返回新链表的头节点
return map[head];
}
};
int main(){
vector<vector<int>> a={{7,-1},{13,0},{11,4},{10,2},{1,0}};
Solution1 s;
//创建原随机链表
Node *head =createNode(a);
//随机链表的复制
Node *test =s.copyRandomList1(head);
//只输出复制随机链表的random指向
while(test!=nullptr){
if(test->random!=nullptr){
cout<<test->random->val<<" ";
}else{
cout<<"nullptr ";
}
test=test->next;
}
cout<<"null";
return 0;
}
LeetCode 面试经典 150_链表_随机链表的复制(59_138)原题链接
欢迎大家和我沟通交流(✿◠‿◠)