文章目录
- Tag
- 题目来源
- 题目解读
- 解题思路
- 方法一:哈希表+递归
- 方法二:哈希表
- 方法三:迭代+拆分节点
- 写在最后
Tag
【递归】【迭代】【链表】
题目来源
138. 随机链表的复制
题目解读
对一个带有随机指向的链表进行深拷贝操作。
解题思路
本题一共有三种解法,分别是:
- 哈希表+递归;
- 哈希表;
- 迭代+节点拆分。
前两种方法都需要用到哈希表这种基本的数据结构,哈希表中存放的数据都是源节点与深拷贝后的节点这样的键值对,但是一个(方法一)是使用递归进行完成深拷贝任务,而另一个(方法二)使用迭代来完成深拷贝工作,这两种方法属于常规解法了。
第三种解法比较巧妙,省去了哈希表,节省了空间,面试的时候能够清晰的答出该方法一定可以令面试官眼前一亮。
接下来具体看一看这三种解法。
方法一:哈希表+递归
如果是对一个普通的链表进行深拷贝操作,我们直接按照遍历的顺序创建链表即可。但是,本题中的链表有一个随机指针,指向链表中的随机的一个节点,如果还是按照链表顺序拷贝节点,当前拷贝的节点的随机节点可能还没有创建。
于是会有两种不同的思路,一是通过递归的方法创建需要的节点,具体地:
- 维护一个哈希表
cacheNode
用来存放源节点与深拷贝后的节点; - 首先创建头结点的拷贝节点,并存入到哈希表中;
- 接着递归建立新头结点的下一个节点与随机节点,也就是调用自身;
- 最后返回
cacheNode[head]
。
实现代码
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
unordered_map<Node*, Node*> cacheNode;
Node* copyRandomList(Node* head) {
if (head == NULL) {
return NULL;
}
if (!cacheNode.count(head)) {
Node* newHead = new Node(head->val);
cacheNode[head] = newHead;
newHead->next = copyRandomList(head->next);
newHead->random = copyRandomList(head->random);
}
return cacheNode[head];
}
};
复杂度分析
时间复杂度: O ( n ) O(n) O(n), n n n 是链表的长度。
空间复杂度: O ( n ) O(n) O(n)。
方法二:哈希表
直接使用根据每个节点拷贝出新的节点,存放到哈希表中,然后根据原链表的指向关系更新新节点的指向关系。
实现代码
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
unordered_map<Node*, Node*> cacheNode;
Node* copyRandomList(Node* head) {
if (head == NULL) {
return NULL;
}
Node* curr = head;
while (curr != NULL) {
cacheNode[curr] = new Node(curr->val);
curr = curr->next;
}
curr = head;
while (curr != NULL) {
cacheNode[curr]->next = cacheNode[curr->next];
cacheNode[curr]->random = cacheNode[curr->random];
curr = curr->next;
}
return cacheNode[head];
}
};
复杂度分析
时间复杂度: O ( n ) O(n) O(n), n n n 是链表的长度。
空间复杂度: O ( n ) O(n) O(n)。
方法三:迭代+拆分节点
方法三和方法二类似,都是先将原链表拷贝一次,只是本方法是先将拷贝后的链表先连在原链表后,接着就是巧妙的连接与拆分方法,接下来以图示的方式进行分析:
原链表为 A->B->C
。
深拷贝节点并连接next指针
我们先在原链表的节点之后深拷贝一个新的节点并连接,生成一个新的链表 A->A'->B->B'->C->C'
。实现的方法与在链表指定节点 node
之后插入一个新的节点类似:
- 先深拷贝节点
node
得到新的节点newNode
; - 再将拷贝得到的新节点
newNode
连接到node->next
; - 最后将
node
连接到newNode
。
连接random指针
现在需要解决拷贝后的链表的 random
指针指向问题,node
的下一个节点就是 newNode
,newNode
的 random
指针指向就是 node
的 random
指向,我们就根据 node
来更新就可以。具体实现见代码。
切断两链表之间的联系
解决好 random
指针指向问题,就要切断两个链表之间的联系。具体实现见代码
实现代码
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == NULL) {
return head;
}
// 深拷贝节点并连接next指针
for (Node* node = head; node != NULL; node = node->next->next) {
Node* newNode = new Node(node->val);
newNode->next = node->next;
node->next = newNode;
}
// 连接random指针
for (Node* node = head; node != NULL; node = node->next->next) {
Node* newNode = node->next;
newNode->random = (node->random != NULL) ? node->random->next : NULL;
}
// 切断两链表之间的联系
Node* newHead = head->next;
for (Node* node = head; node != NULL; node = node->next) {
Node* newNode = node->next;
node->next = node->next->next;
newNode->next = (newNode->next != NULL) ? newNode->next->next : NULL;
}
return newHead;
}
};
时间复杂度: O ( n ) O(n) O(n), n n n 是链表的长度。
空间复杂度: O ( 1 ) O(1) O(1),答案链表不算作额外的空间。
写在最后
如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。
如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。
最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。