Copy List with Random Pointer

Problem:

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.

/**
 * Definition for singly-linked list with a random pointer.
 * struct RandomListNode {
 *     int label;
 *     RandomListNode *next, *random;
 *     RandomListNode(int x) : label(x), next(NULL), random(NULL) {}
 * };
 */

什么意思呢,就是给定了一个链表,要返回这个链表的深度拷贝,就是申请新的空间,制造一份结点关系及结点lable值都一样的链表,同时最终还别破换原来的链表,要不复制也没意义了。

所谓复制,就是给个什么弄个一样的什么就行了,在现实中,我们发现,镜像这个东西,就是一模一样的。所以我们先来尝试下镜像。

一、镜像法:

1)首先不管三七二十一先遍历给定链表的同时复制一份。如 

 Copy List with Random Pointer_第1张图片

现在这种情形若是没有红色部分代表的random指针信息,就已经完成了,那样这个问题也就没什么意思了,难点就在怎么复制random信息,即令1‘ 的random指针指向3’,2‘的random指针也指向3’。我们知道,要是数组的话这个问题就好解决,只要知道跨度就可以令新的结点也跨过相同的跨度就可以了,但是通过new操作得到的指针就无能为力了,获得的地址很随机,没那种特性。别灰心,想想我们新建的链表的random指针还没用呢,虽然不能一步到位的指向应该指向的结点,但总是可以用的上。

2)我们让它们指向与其成镜像的结点,得到下面的情形。

Copy List with Random Pointer_第2张图片

走到这一步好像还看不到希望,别急,开始不是说到镜像了吗,仔细观察,我们发现,1与1‘,3与3’,对,就是镜像关系,我虽然不知道从1到3的跨度,但我们知道,3的镜像肯定就是1‘的random要指向的结点,也就是3’。那么要是3有个指针指向了3‘就好了,而且这种操作也是可以在复制的时候完成的。考虑到每个结点有两种类型的指针,一个是next,一个是random,那么我们要哪类指针来完成这个指向呢?random肯定不行,因为这样的话,在我们没复制random关系前,原来的关系就被我们破坏了,指没了,那就只能是next了,谁让咱们熟悉它呢。

3)接下来的情形如下:

Copy List with Random Pointer_第3张图片

其实,上一步我们选择next的合理解释是,此时的next已经没有多大的意义了,因为即使1的next指针不指向2我们也可以通过拷贝的链表来找到它,即通过2’的random来找到2。所以,next指针别委屈,是你真的太合适了。感谢天,感谢地,感谢阳光照大地,到了这里问题就基本解决了。

那么1‘的random是谁呢?是1’->random->random->next,同样2‘的random也就是2’->random->random->next。

4)但是先别急,我们现在还不能只顾着修改新建链表的random指针,因为此时我们看到原来链表已经被我们破坏掉了,next指针已经指跑偏了。所以的先通过random把原来链表的next指针复位,这就告诉我们,做人要厚道,总不能过河拆桥,卸磨杀驴。

复位操作:1->next = 1->next->next->random, 2->next = 2->next->next->random。

我们已经分析了random的更改操作1’->random->random->next,同样2‘的random也就是2’->random->random->next。所以这里我们需要这二者同步进行修改。比如上图中,我们先通过next和random的配合,找到2,用q指向它,它就是将要建立的1的next,这时我们就可以修改1‘ 的random,以及1的next。同理指针后移,到了2和2’ 处,首先通过2的next和2‘ 的random来找到2的next节点,用q来指向这个节点,也就是3,然后修改2的next,以及2’ 的random。

最终我们得到:

Copy List with Random Pointer_第4张图片

5)看起来很完美是不,不用借用额外的存储空间,关靠指针的相互利用就可以,但是还是有一种情况我们没有考虑到。

Copy List with Random Pointer_第5张图片

在random指向后面的节点甚至是其自身,上面的分析都没问题,但当random指向前面的节点的时候,就出问题了。

按着上面的分析我们从1开始,1 没问题,2 也没问题,当处理 3 的时候,我们发现指向跑偏了,指向2 了,这是因为我们在处理 3 之前修改了 前面的next 域,而在后面又用到了这个域,当然就不对了,看来这个next不能在处理random的同时修改,要等到所有的random指针操作完成后,在归位原来的next域,但这时next 域就完全的找不到了,所以,我们还的在开始破坏next域之前,保存next关系。

//code

class Solution{
public:
	 RandomListNode *copyRandomList(RandomListNode *head) {
		 if(head == NULL) 
			 return NULL;
		 RandomListNode *pold = head;
		 RandomListNode *pnew_head = NULL;
		 RandomListNode *pnew = NULL;
		 vector<RandomListNode*> copy_list;
		 //step 1 & 2 & 3
		 while (pold != NULL)
		 {
		     copy_list.push_back(pold);//store the next relationship
			 RandomListNode *next_old = pold->next;
			 RandomListNode *qnew = new RandomListNode(pold->label);
			 if(pnew_head == NULL)//first node.
		     {
				pnew_head = pnew = qnew;
				pnew->random = pold;
				pold->next = pnew;
			 }
			 else
			 {
				pnew->next = qnew;
				qnew->random = pold;
				pold->next = qnew;
				pnew = qnew;
			 }
			 pold = next_old;
		 }
		 // 4
		 pnew = pnew_head;
	
		 while (pnew)//link the random pointer.
		 {
			 if(pnew->random && pnew->random->random != NULL)
			 pnew->random = pnew->random->random->next;
			 else
				 pnew->random = NULL;
			 pnew = pnew->next;
		 }
		 //recover the next point of origial
		 vector<RandomListNode*>::iterator iter = copy_list.begin();
		 while((iter + 1)!= copy_list.end())
		 {
		     (*iter)->next = (*(iter + 1));
		     ++iter;
		 }
		 (*iter)->next = NULL;//the last one.
		 return pnew_head;
	 }
};


这个题目的程序网上很多,这个解法也很多,但大多说的不是很详细,这个解法的得来过程更是含糊不清,这里说的有点啰嗦,但作者觉得也还算详细,希望对读者有一丝的启发,图画的很草,但总算能看的清楚,这就足够了,有什么问题请留言,欢迎不吝指教。


你可能感兴趣的:(LeetCode,C++,链表,copy,Random,deep)