题目:
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.
题目分析:题目中给出一个链表,这个链表很特殊在于。除了正常的next指针,每个节点还有一个random指针可以指向链表中任何节点(包括自己)或者null。现在要求深度拷贝一个这样的链表。即所有节点的label都和原链表一样,next指针和random指针所指的节点也都和原链表的一一对应。另外,在做题的过程中发现,O(n^2)的算法,系统会提示TimeLimitedExceed错误,必须要找出更快的算法才可以。
思路:
本题的难点在于,如何找到新链表中节点random指针所对应的节点。以两个链表的头结点head和head2为例,已知head.random,怎么在新链表中找到head2.random对应的节点?最好的办法,可能就是判断head.random指向的节点和head结点的相对距离,来确定head2.random和head2相对距离。
最开始,想到一个办法是同步遍历。让traverse1从head开始,traverse2从head2开始,同步遍历(traverse1走一步,traverse2也走一步)。当traverse1走到head.random对应的节点时,traverse2也就走到了head2.random对应的节点,把traverse2对应的节点赋给head2.random即可。对于head2之外的节点,可以使用同样的方法。但是,这个算法是O(n^2),在LeetCode中没有通过,提示TimeLimitedExceed错误。(这一段代码贴在本文最后方)
于是想,能不能不用遍历,就能找到head2.random对应节点和head2的相对距离呢?是可以的。可以通过这些指针所在地址来确定位置。例如(&head.random-&head), 我们就知道head.random和head的地址距离差。然后&head2.random = &head2 +(&head.random-&head) 就能确定号head2.random对应的位置。这是个初步的想法,不确定一定就能实现。关键是,java中禁止读取指针的地址,所以这个办法或许可以在c中实现,在java中是绝对不能的。
最后想到的解决办法是,建立一个Hash表。让原链表中每个节点分别作为key,让新链表中每个节点分别对应作为value,例如<head,head2>就是Hash表中的一个pair。这样,当我们读取到head.random节点的时候,可以直接通过Hash表得到head2.random对应的那个节点了。具体算法如下:
- 先浅度复制一个这样的链表
- 复制每个节点,以及next指针对应的连接
- 并且,让原链表中节点traverse1和新链表中节点traverse2成为一个Hash对,放入Hash表map中
- 节点的random指针都先指向null。
- 后面的步骤即安排每个节点的random指针指向哪个节点
- traverse1和traverse2分别为两个链表中对应的节点
- traverse1.random如果为null,直接让traverse2.random也为null,退出进行下一个节点
- 通过map.get(traverse1.random)找到traverse1.random对应节点在新链表中相对应的节点,即是traverse2.random应该指向的节点
注意点:
- 还想过,把两个链表中各节点放到两个数组中,即array1[]和array2[]中。通过找到traverse1.random在array1中的index,从而在array2中找到index对应的节点,赋给traverse2.random。但是,查看API文档,Array类没有indexOf之类的方法能得到index的(系统应该可以通过地址判断出index),所以只能通过遍历数组得到index。这样,算法还是O(n^2),没有改进。作罢。
代码:(Hash表方法)
/**
* Definition for singly-linked list with a random pointer.
* class RandomListNode {
* int label;
* RandomListNode next, random;
* RandomListNode(int x) { this.label = x; }
* };
*/
import java.util.HashMap;
public class Solution {
public RandomListNode copyRandomList(RandomListNode head) {
if (head == null){
return null;
}
RandomListNode head2, traverse1, traverse2;
HashMap<RandomListNode,RandomListNode> map = new HashMap<RandomListNode,RandomListNode>();
head2 = new RandomListNode(head.label);
traverse1 = head. next;
traverse2 = head2;
map.put(head,head2);
while (traverse1 != null){
traverse2.next = new RandomListNode(traverse1.label);
traverse2 = traverse2.next;
map.put(traverse1,traverse2);
traverse1 = traverse1.next;
}
traverse1 = head;
traverse2 = head2;
while(traverse1 != null){
if(traverse1.random == null){
traverse2.random = null;
traverse1 = traverse1.next;
traverse2 = traverse2.next;
continue;
}
traverse2.random = map.get(traverse1.random);
traverse1 = traverse1.next;
traverse2 = traverse2.next;
}
return head2;
}
}
代码:(同步遍历方法)
这个代码在LeetCode上又TimeLimitedExceed错误,但是逻辑上应该可以运行。
/**
* Definition for singly-linked list with a random pointer.
* class RandomListNode {
* int label;
* RandomListNode next, random;
* RandomListNode(int x) { this.label = x; }
* };
*/
import java.util.*;
public class Solution {
public RandomListNode copyRandomList(RandomListNode head) {
if (head == null){
return null;
}
RandomListNode head2, traverse1, traverse2;
List<RandomListNode> arrayList1 = new ArrayList<RandomListNode>();
List<RandomListNode> arrayList2 = new ArrayList<RandomListNode>();
head2 = new RandomListNode(head.label);
traverse1 = head. next;
traverse2 = head2;
arrayList1.add(head);
arrayList2.add(head2);
while (traverse1 != null){
traverse2.next = new RandomListNode(traverse1.label);
traverse2 = traverse2.next;
arrayList1.add(traverse1);
arrayList2.add(traverse2);
traverse1 = traverse1.next;
}
traverse1 = head;
traverse2 = head2;
while(traverse1 != null){
if(traverse1.random == null){
traverse2.random = null;
traverse1 = traverse1.next;
traverse2 = traverse2.next;
continue;
}
traverse2.random = arrayList2.get(arrayList1.indexOf(traverse1.random));
traverse1 = traverse1.next;
traverse2 = traverse2.next;
}
return head2;
}
}