题目
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路
首先要对题目中的复杂链表进行理解,可以根据题目中我们可以理解到这个复杂链表不像我们以前的链表那样,只是单纯的从前指向后面的关系,还包含了一个随机的random指针指向链表中的一个随机节点
random a b c如上图所示,这就是一个复杂链表,对于节点a他不止只有指向下一个节点b,他还有一个指向节点c的指针random
对于这个题目的解法有两种
- 用map保存旧节点和新节点之间的映射关系,如果节点已经存在那么使用存在的节点即可,如果不存在那么需要新开辟节点并存储他们之间的映射关系
- 对原链表进行完全的复制(包含数据跟节点关系),然后进行原链表跟新链表的分离
方法一
首先我们需要了解map
这个Java集合的作用,其中map
是一个包含有键值对的数据的集合,关系可以对于为key-value
,并且可以通过查找key
来查找到value
。
import java.util.Map;
import java.util.HashMap;
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead == null)return null;
RandomListNode newHead = null;//构造一个新的链表的头节点,初始化为null
RandomListNode p = pHead;//指向当前链表的头节点位置,由于头节点pHead不能随便移动,所以找一个中间变量p来指代头节点的位置
RandomListNode q = null;//这个q跟上面的p类似,都是指向头节点的中间变量,方便进行链表的节点操作
Map<RandomListNode, RandomListNode> map = new HashMap<>();//这里用新旧节点来进行一个键值对的映射
while(p != null){
if(newHead == null){
newHead = new RandomListNode(pHead.label);
q = newHead;
map.put(pHead, newHead);//上面第一个if里面的判断是看新的链表的头节点如果为空的化,
//直接将原链表中头节点的值赋给新链表,并且构建新旧链表的键值对映射
}else{
if(p.next!= null && map.containsKey(p.next))
q.next = map.get(p.next);//这里是如果新链表的头节点不为空,
//并且旧链表的下一个节点也不为空并且在map中可以查到该节点的键值对中的key值,
//那么就可以直接将value提取出来,该值就是新链表中当前节点的下一个节点的值
else{
if(p.next!= null){
RandomListNode temp = new RandomListNode(p.next.label);
map.put(p.next, temp);
q.next = temp;
}//这里是如果旧链表的当前节点的下一个节点不为null,但是map中也找不到该键值对的映射,
//那就只有自己进行新链表的赋值,并且从新构建键值对的映射
}
if(p.random != null && map.containsKey(p.random))
q.random = map.get(p.random);
else{
if(p.random != null){
RandomListNode temp = new RandomListNode(p.random.label);
map.put(p.random, temp);
q.random = temp;
}
}//这里的操作原理跟上面类似,但是针对的是random指针
p = p.next;
q = q.next;
}
}
return newHead;
}
}
这种方法的优点是可以利用map
构建键值对的映射,大大的节省了时间,但是需要另外开辟新的map
空间,在牛客网上提交后的结果是
方法二
- 进行原先链表的复制,新复制出来的节点就插在原来节点的后面
- 然后对random指针的复制
- 将复制后的链表剥离出来
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
if(pHead == null) {
return null;
}
RandomListNode currentNode = pHead;
//1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
while(currentNode != null){
RandomListNode cloneNode = new RandomListNode(currentNode.label);
RandomListNode nextNode = currentNode.next;
currentNode.next = cloneNode;
cloneNode.next = nextNode;
currentNode = nextNode;
}
currentNode = pHead;
//2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
while(currentNode != null) {
currentNode.next.random = currentNode.random==null?null:currentNode.random.next;
currentNode = currentNode.next.next;
}
//3、拆分链表,将链表拆分为原链表和复制后的链表
currentNode = pHead;
RandomListNode pCloneHead = pHead.next;
while(currentNode != null) {
RandomListNode cloneNode = currentNode.next;
currentNode.next = cloneNode.next;
cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
currentNode = currentNode.next;
}
return pCloneHead;
}
}
这种方法便于理解,但是要对链表进行三次遍历,所以时间复杂度也就更加的大
总结
通过这个题目就可以掌握到链表的深拷贝这个方法。
-
第一种方法是只用对原链表进行一次遍历,每次遍历的时候通过判断当前节点的下一位节点是否为空来进行节点值的copy,但是需要定义额外的新链表,时间复杂度低,但是空间复杂度大。
-
第二种方法是在原来的链表直接进行复制的操作,然后进行指针的错位调整,从而形成新的链表,然后进行新旧链表的分离,虽然该方法不需要从新花费空间来定义一个新链表,但是需要对原先链表遍历两次,所花费的时间开销大一些。