题目描述
描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
示例:
输入:{1,2,3,4,5,3,5,#,2,#}
输出:{1,2,3,4,5,3,5,#,2,#}
解析:我们将链表分为两段,前半部分{1,2,3,4,5}为ListNode,后半部分{3,5,#,2,#}是随机指针域表示。
以上示例前半部分可以表示链表为的ListNode:1->2->3->4->5;后半部分,3,5,#,2,#分别的表示为1的位置指向3,2的位置指向5,3的位置指向null,4的位置指向2,5的位置指向null
如下图:
输入:{1,2,3,4,5,3,5,#,2,#}
返回值:{1,2,3,4,5,3,5,#,2,#}
解题思路
复杂链表的复制:最直观的想法是,如果没有随机指针,那么直接进行尾插法即可,如果寻找链表的中间位置,那么使用快慢指针其中每次快指针走两步慢指针走一步即可,但是随机指针可能指向空,如何判断是尾指针呢?深拷贝后随机指针与原指针不一致,如何实现映射呢?方法来啦!虽然随机指针可能指向空,但是前半部分原链表各个节点均会出现且不为空,线性遍历判断是否为空是不可能的,所以此处使用一个map映射,这样只会将原链表部分映射一次即可,注意,此处的映射是原链表节点和深拷贝节点的映射,因为原链表的链表指针以及随机指针均有。
RandomListNode* Clone(RandomListNode* pHead)
{
//创建头结点
RandomListNode *head=new RandomListNode(-1);
head->next=nullptr;
head->random=nullptr;
//尾结点
RandomListNode *tail=head;
//当前结点
RandomListNode *cur=pHead;
//原链表结点与深拷贝结点的映射
unordered_map<RandomListNode *,RandomListNode *> umap;
//当当前结点不为空并且没有映射 创建原链表结点并且实现next
while(cur&&umap.find(cur)==umap.end())
{
//拷贝结点
RandomListNode *dcopy=new RandomListNode(cur->label);
dcopy->next=nullptr;
//衔接结点
tail->next=dcopy;
//映射结点
umap[cur]=dcopy;
//移动结点
tail=tail->next;
cur=cur->next;
}
//根据映射完成原链表节点的random指针
for(auto [key,value]:umap)
{
value->random=key->random==nullptr?nullptr:umap[key->random];
}
return head->next;
}
优化:上述方法我们使用了哈希表即使用了辅助空间,那么能不能写出原地实现的方法呢?当然可以!我们首先遍历原链表并创建拷贝链表,将拷贝链表插在对应的原链表之后,得到形如原1-拷1-原2-拷2…的链表,然后通过原链表的随机指针来设置拷贝链表的随机指针,接着将原链表和拷贝链表进行拆分出来,即一个指针指向原链表第一个结点,一个指针指向拷贝链表第一个结点,然后两个指针均每次移动两个位置即可实现。
RandomListNode* Clone(RandomListNode* pHead)
{
if(!pHead) return NULL; //空
//拷贝结点
RandomListNode *cur=pHead;
while(cur)
{
//创建结点
RandomListNode *Node=new RandomListNode(cur->label);
Node->next=NULL;
//将拷贝结点插在源结点后面
Node->next=cur->next;
cur->next=Node;
//更新当前结点为拷贝结点的下一位
cur=Node->next;
}
//处理拷贝结点的随机指针
RandomListNode *old=pHead,*newn=pHead->next;
while(old)
{
//将原链表的随机指针赋值给拷贝链表随机指针
newn->random=old->random==NULL?NULL:old->random->next;
//原链表走两步
if(old->next) old=old->next->next;
//拷贝链表走两步
if(newn->next) newn=newn->next->next;
}
//分离出原链表和拷贝链表
old=pHead;
newn=pHead->next;
//拷贝链表头结点
RandomListNode *head=newn;
while(old)
{
if(old->next) old->next=old->next->next;
if(newn->next) newn->next=newn->next->next;
old=old->next;
newn=newn->next;
}
return head;
}