【小白爬Leetcode138】1.7 复制带随机指针的链表 Copy List with Random Pointer
Leetcode 138 m e d i u m \color{#FF7D00}{medium} medium
题目
做题之前:
如果对DeepCopy 深拷贝不理解的读者,需要补一下这方面的知识,或者看懂我的这句话:
一个变量 a 对应着一块内存空间,如果我现在要拷贝这个变量a,那么我有两种选择:
1.浅拷贝:新建一个变量b,指向变量a的内存空间的地址。这样&a == &b,当变量a发生改变的时候,b也会相应改变(因为二者指向的都是同一块内存空间);一般默认的赋值运算符=也是默认浅拷贝。
2.深拷贝:新建一个变量b,另开辟一块内存空间,把a的值拷贝过来。这样&a != &b,当变量a发生改变的时候,b是不会收到影响的。
Description
A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.
Return a deep copy of the list.
The Linked List is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:
中文描述
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝。
我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
思路一 用map映射
try1:构建两个地址:序号的map
C++中的 map 和Python中的 dict 相似,都是储存键值对映射关系的数据结构。
想法:
这道题的难点在于:由于链表不可以随机访问,只能遍历访问,那么如何拷贝random指针?难道每一个节点的random的确定都要都要遍历一次链表?这样时间复杂度就变成了可怕的O(n2)。
我们不妨牺牲一些空间复杂度,用一个map容器来储存原链表和新链表的节点地址:序号,如0x00AA:0 表示0x00AA这个内存储存的是第0个节点。
这样我们总共需要遍历两次链表:
第一个while循环:
做三件事:
1.构建原来链表地址:位置的map;
2.构建新链表的地址:位置的map;
3.构建新链表的顺序结构。
第二个while循环:
做三件事:
1.找出原链表random指针的index; 直接 map[key] 即可
2.通过index找出新链表中的地址 ;需要遍历map来获得
3.构建新链表的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){return NULL;}
std::map<Node*,int> node_map_origin;
std::map<Node*,int> node_map_new;
int index = 0;
Node* cur_origin = head;
Node* cur_new = new Node(cur_origin->val);
Node* head_new = cur_new; //记录下新链表的开头
//第一遍遍历原链表,做三件事:
while(cur_origin)
{
//1.构建原来链表地址:位置的map;
node_map_origin[cur_origin] = index;
//2.构建新链表的地址:位置的map;
node_map_new[cur_new] = index;
//3.构建新链表的顺序结构;
if(cur_origin->next)
{
cur_new->next = new Node(cur_origin->next->val);
}
else{cur_new->next = NULL;}
//4.继续向下遍历原链表,注意新链表也得向下遍历
cur_origin = cur_origin->next;
cur_new = cur_new->next;
//5.位置加一
index++;
}
//第二遍遍历原链表,做三件事:
cur_origin = head;
cur_new = head_new;
while(cur_new)
{
//1.找出原链表random指针的index
Node* ptr = NULL;
if(cur_origin->random) //如果random不为NULL;
{
//找到index
index = node_map_origin[cur_origin->random];
//2.通过index找出新链表中的地址
for(std::map<Node*,int>::iterator it = node_map_new.begin();it!=node_map_new.end();it++)
{
if(it->second == index)
{
ptr = it->first;
}
}
}
//3.构建新链表的random
cur_new->random = ptr;
cur_new= cur_new->next;
cur_origin = cur_origin->next;
}
return head_new;
}
};
时间复杂度:O(n); 空间复杂度O(n)
结果惨淡,应该是哪里不巧妙。
try2:map1映射地址:序号,map2映射序号:地址
方法一的速度为什么这么慢呢?试图寻找答案:看了别人的思路,发现我在构建map的时候存在一个问题:
在node_map_new中通过序号找地址的时候,相当于是通过value来找key,这样需要遍历字典,不够简洁。换了一下map的结构:
node_map_origin 是地址:序号的形式,而map2中是序号:地址的形式,这样两次查找都可以直接用map的特性,即通过key找value:
修改后的源码如下:
class Solution {
public:
Node* copyRandomList(Node* head) {
if(!head){return NULL;}
std::map<Node*,int> node_map_origin;
std::map<int,Node*> node_map_new;
int index = 0;
Node* cur_origin = head;
Node* cur_new = new Node(cur_origin->val);
Node* head_new = cur_new; //记录下新链表的开头
//第一遍遍历原链表,做三件事:
while(cur_origin)
{
//1.构建原来链表地址:位置的map;
node_map_origin[cur_origin] = index;
//2.构建新链表的地址:位置的map;
node_map_new[index] = cur_new;
//3.构建新链表的顺序结构;
if(cur_origin->next)
{
cur_new->next = new Node(cur_origin->next->val);
}
else{cur_new->next = NULL;}
//4.继续向下遍历原链表,注意新链表也得向下遍历
cur_origin = cur_origin->next;
cur_new = cur_new->next;
//5.位置加一
index++;
}
//第二遍遍历原链表,做三件事:
cur_origin = head;
cur_new = head_new;
while(cur_new)
{
//1.找出原链表random指针的index
Node* ptr = NULL;
if(cur_origin->random) //如果random不为NULL;
{
//找到index
index = node_map_origin[cur_origin->random];
//2.通过index找出新链表中的地址
ptr = node_map_new[index];
}
//3.构建新链表的random
cur_new->random = ptr;
cur_new= cur_new->next;
cur_origin = cur_origin->next;
}
return head_new;
}
};
结果效率并没有提升多少。大概是amp通过key找val也是要遍历的吧
try3:一个map+一个vector
vector是一个可以实现随机访问的容器,对于第二个通过序号来找地址的情况十分适合。用node_vector_new来替代node_map_new,语法上进行相应调整即可。
修改过程中,我还发现原node_map_new中少了最后一个节点,应该拿出来放在 if 语句外边。C++map找不到对应的key直接是返回最后一个end()指针,是最后一个节点的下一个,没有任何意义。
修改后的代码如下:
class Solution {
public:
Node* copyRandomList(Node* head) {
if(!head){return NULL;}
std::map<Node*,int> node_map_origin;
std::vector<Node*> node_vector_new;
int index = 0;
Node* cur_origin = head;
Node* cur_new = new Node(cur_origin->val);
Node* head_new = cur_new; //记录下新链表的开头
//第一遍遍历原链表,做三件事:
while(cur_origin)
{
//1.构建原来链表地址:位置的map;
node_map_origin[cur_origin] = index;
//2.构建新链表的顺序结构,
if(cur_origin->next)
{
cur_new->next = new Node(cur_origin->next->val); //构建新链表的顺序结构
}
else{cur_new->next = NULL;}
//3.将新链表的地址push进vector里
node_vector_new.push_back(cur_new);
//4.继续向下遍历原链表,注意新链表也得向下遍历
cur_origin = cur_origin->next;
cur_new = cur_new->next;
//5.位置加一
index++;
}
//第二遍遍历原链表,做三件事:
cur_origin = head;
cur_new = head_new;
while(cur_new)
{
//1.找出原链表random指针的index
Node* ptr = NULL;
if(cur_origin->random) //如果random不为NULL;
{
//找到index
index = node_map_origin[cur_origin->random];
//2.通过index找出新链表中的地址
ptr = node_vector_new[index];
}
//3.构建新链表的random
cur_new->random = ptr;
cur_new= cur_new->next;
cur_origin = cur_origin->next;
}
return head_new;
}
};
结果好了很多: