题目及测试
package pid138;
/* 复制带随机指针的链表
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的深拷贝。
示例:
输入:
{"$id":"1","next":{"$id":"2","next":null,"random":{"$ref":"2"},"val":2},"random":{"$ref":"2"},"val":1}
解释:
节点 1 的值是 1,它的下一个指针和随机指针都指向节点 2 。
节点 2 的值是 2,它的下一个指针指向 null,随机指针指向它自己。
*/
public class main {
public static void main(String[] args) {
Node a=new Node();
Node b=new Node();
Node c=new Node();
Node d=new Node();
a.val=1;
b.val=2;
a.next=b;
a.random=b;
b.random=a;
//test(a);
a=new Node();
a.val=1;
//test(a);
a=new Node();
b=new Node();
c=new Node();
d=new Node();
a.val=1;
b.val=2;
c.val=3;
d.val=4;
a.next=b;
b.next=c;
c.next=d;
a.random=c;
b.random=c;
c.random=null;
d.random=null;
test(a);
}
private static void test(Node ito) {
Solution solution = new Solution();
Node rtn;
long begin = System.currentTimeMillis();
System.out.println();
//开始时打印数组
rtn=solution.copyRandomList(ito);//执行程序
long end = System.currentTimeMillis();
System.out.println("rtn=");
while(rtn!=null){
System.out.println(rtn+" "+rtn.val+" "+rtn.next+" "+rtn.random);
rtn=rtn.next;
}
//System.out.println(":rtn" );
//System.out.print(rtn);
System.out.println();
System.out.println("耗时:" + (end - begin) + "ms");
System.out.println("-------------------");
}
}
解法1(成功,2ms,较慢)
先沿着head的next,不断复制节点和节点之间的next关系,建立srcMap和destMap,建立节点与位置之间的关系。
然后沿着head的next,复制节点和节点之间的random关系,得到一个节点srcNow,得到它的random,得到它在原表的位置,根据位置,找到destMap里对应位置的节点,赋值给destNow的random。
package pid138;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/*
//Definition for a Node.
class Node {
public int val;
public Node next;
public Node random;
public Node() {}
public Node(int _val,Node _next,Node _random) {
val = _val;
next = _next;
random = _random;
}
};
*/
class Solution {
public Node copyRandomList(Node head) {
if(head==null){
return null;
}
// 原来的链表,节点与位置的对应关系
Map<Node, Integer> srcMap=new HashMap<>();
// 新的链表,位置与节点的对应关系
Map<Integer, Node> destMap=new HashMap<>();
Node srcNow=head;
Node destFirst=new Node();
Node destNow=destFirst;
// 先深度复制链表的val和next关系
int i=0;
while(srcNow!=null){
Node destNew=new Node();
destNew.val=srcNow.val;
destNow.next=destNew;
destNow=destNow.next;
// 两者的对应关系
srcMap.put(srcNow, i);
destMap.put(i, destNow);
i++;
srcNow=srcNow.next;
}
// 复制random
srcNow=head;
destNow=destFirst.next;
while(srcNow!=null){
if(srcNow.random==null){
srcNow=srcNow.next;
destNow=destNow.next;
continue;
}
i=srcMap.get(srcNow.random);
destNow.random=destMap.get(i);
srcNow=srcNow.next;
destNow=destNow.next;
}
return destFirst.next;
}
}
解法2(别人的)
回溯算法的第一想法是将链表想象成一张图。链表中每个节点都有 2 个指针(图中的边)。因为随机指针给图结构添加了随机性,所以我们可能会访问相同的节点多次,这样就形成了环。
此方法中,我们只需要遍历整个图并拷贝它。拷贝的意思是每当遇到一个新的未访问过的节点,你都需要创造一个新的节点。遍历按照深度优先进行。我们需要在回溯的过程中记录已经访问过的节点,否则因为随机指针的存在我们可能会产生死循环。
从 头 指针开始遍历整个图。
我们将链表看做一张图。下图对应的是上面的有向链表的例子,Head 是图的出发节点。
当我们遍历到某个点时,如果我们已经有了当前节点的一个拷贝,我们不需要重复进行拷贝。
如果我们还没拷贝过当前节点,我们创造一个新的节点,并把该节点放到已访问字典中,即:
visited_dictionary[current_node] = cloned_node_for_current_node.
我们针对两种情况进行回溯调用:一个顺着 random 指针调用,另一个沿着 next 指针调用。
空间复杂度:O(N) 。如果我们仔细分析,我们需要维护一个回溯的栈,同时也需要记录已经被深拷贝过的节点,也就是维护一个已访问字典。渐进时间复杂度为 O(N) 。
public class Solution {
// HashMap which holds old nodes as keys and new nodes as its values.
HashMap<Node, Node> visitedHash = new HashMap<Node, Node>();
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
// If we have already processed the current node, then we simply return the cloned version of
// it.
if (this.visitedHash.containsKey(head)) {
return this.visitedHash.get(head);
}
// Create a new node with the value same as old node. (i.e. copy the node)
Node node = new Node(head.val, null, null);
// Save this value in the hash map. This is needed since there might be
// loops during traversal due to randomness of random pointers and this would help us avoid
// them.
this.visitedHash.put(head, node);
// Recursively copy the remaining linked list starting once from the next pointer and then from
// the random pointer.
// Thus we have two independent recursive calls.
// Finally we update the next and random pointers for the new node created.
node.next = this.copyRandomList(head.next);
node.random = this.copyRandomList(head.random);
return node;
}
}
解法3(别人的)
迭代算法不需要将链表视为一个图。当我们在迭代链表时,我们只需要为 random
指针和 next
指针指向的未访问过节点创造新的节点并赋值即可。
与回溯法相比,不在复制节点的方法里,递归赋值next和random,而是沿着原来的next方向,一路复制过去。空间复杂度也是O(n)
public class Solution {
// Visited dictionary to hold old node reference as "key" and new node reference as the "value"
HashMap<Node, Node> visited = new HashMap<Node, Node>();
public Node getClonedNode(Node node) {
// If the node exists then
if (node != null) {
// Check if the node is in the visited dictionary
if (this.visited.containsKey(node)) {
// If its in the visited dictionary then return the new node reference from the dictionary
return this.visited.get(node);
} else {
// Otherwise create a new node, add to the dictionary and return it
this.visited.put(node, new Node(node.val, null, null));
return this.visited.get(node);
}
}
return null;
}
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
Node oldNode = head;
// Creating the new head node.
Node newNode = new Node(oldNode.val);
this.visited.put(oldNode, newNode);
// Iterate on the linked list until all nodes are cloned.
while (oldNode != null) {
// Get the clones of the nodes referenced by random and next pointers.
newNode.random = this.getClonedNode(oldNode.random);
newNode.next = this.getClonedNode(oldNode.next);
// Move one step ahead in the linked list.
oldNode = oldNode.next;
newNode = newNode.next;
}
return this.visited.get(head);
}
}
解法4(成功,0ms,极快)
与上面提到的维护一个旧节点和新节点对应的字典不同,我们通过扭曲原来的链表,并将每个拷贝节点都放在原来对应节点的旁边。这种旧节点和新节点交错的方法让我们可以在不需要额外空间的情况下解决这个问题。让我们看看这个算法如何工作
遍历原来的链表并拷贝每一个节点,将拷贝节点放在原来节点的旁边,创造出一个旧节点和新节点交错的链表。
如你所见,我们只是用了原来节点的值拷贝出新的节点。原节点 next 指向的都是新创造出来的节点。
cloned_node.next = original_node.next
original_node.next = cloned_node
迭代这个新旧节点交错的链表,并用旧节点的 random 指针去更新对应新节点的 random 指针。比方说, B 的 random 指针指向 A ,意味着 B' 的 random 指针指向 A' 。
现在 random
指针已经被赋值给正确的节点, next
指针也需要被正确赋值,以便将新的节点正确链接同时将旧节点重新正确链接。
public Node copyRandomList(Node head) {
if(head==null){
return null;
}
// 复制next节点,到节点后面
Node srcNow = head;
while(srcNow != null) {
Node destNow = new Node(srcNow.val, srcNow.next, null);
srcNow.next = destNow;
srcNow = destNow.next;
}
// 复制随机节点
srcNow = head;
while(srcNow != null) {
Node destNow = srcNow.next;
if(srcNow.random != null) {
destNow.random = srcNow.random.next;
}
srcNow = destNow.next;
}
// 拆分
srcNow = head;
Node destHead = srcNow.next;
Node destNow = srcNow.next;
while(srcNow != null) {
srcNow.next = destNow.next;
if(destNow.next != null) {
destNow.next = destNow.next.next;
}
srcNow = srcNow.next;
destNow = destNow.next;
}
return destHead;
}