题目
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
思路
-
使用哈希表/散列表(hashmap)的方式解题
大致思路如下:
将整个复制的过程分为两部分:节点的复制,指针域的复制
1、首先进行节点的复制:
设置一个指针cur对head链表进行遍历,遍历的同时,完成对于head链表的节点的复制(此时,对于新创建的节点,只设置它的val属性的值)
采用哈希表(hashmap),来存储head链表中的节点与其对应的新创建的节点之间的,对应关系(便于2、中 进行指针域的指向的复制)
2、进行节点的指针域(next域、random域)的复制:
遍历head链表,对于它的每个节点cur的指针域(cur.next、cur.random),通过hashmap的get方法,获取与之对应的新创建的节点;对于cur节点本身也使用get方法,获取与之对应的新创建的节点。=>使得与cur对应的新创建的节点 的指针域(next、random)均指向它们各自的节点(也是新创建的节点):
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
==>因为此时已经将head链表中的所有节点都进行过复制,并且存储在hashmap中了,所以在此时进行指针域的设置,就不会存在所指向的节点不存在的情况了。 -
先将新的链表存储head链表中,最后进行两个链表的分离
=>按照3步来进行:
1、在链表head中完成对于该链表中所有节点的复制(不包含random指针域的复制)
2、在链表head中,实现对于复制节点的random指针域的赋值
3、将复制的节点从head链表中抽离出来,并将head链表也还原成原来的样子
实现
- 使用哈希表/散列表(hashmap)的方式解题
class Solution {
public Node copyRandomList(Node head) {
//先考虑特殊情况:head的链表为空
if(head == null){
return null;
}
//常规情况的处理方式
Node cur = head;//用于遍历head链表的指针
HashMap<Node,Node> map = new HashMap();
//1、首先进行节点的复制:
while(cur != null){//没有遍历完head链表
map.put(cur, new Node(cur.val));//完成复制操作
cur = cur.next;//遍历操作
}
//2、进行节点的指针域(next域、random域)的复制:
cur = head;//开始新一轮,对于head链表地遍历
while(cur != null){
map.get(cur).next = map.get(cur.next);//指针域的复制
map.get(cur).random = map.get(cur.random);//指针域的复制
cur = cur.next;//遍历
}
return map.get(head);//返回的是head节点 对应的复制得到的节点(新链表的头节点)
}
}
时间复杂度:O(n)
空间复杂度:O(n)
- 先将新的链表存储head链表中,最后进行两个链表的分离
==>代码写起来比使用哈希表的方式简单,但是:空间复杂度低,为常数阶O(1)
class Solution {
public Node copyRandomList(Node head) {
//按照3步来进行:
//1、在链表head中完成对于该链表中所有节点的复制(不包含random指针域的复制)
head = copyBase(head);//返回的完成节点赋值的链表,仍用head指针指向它
//2、在链表head中,实现对于复制节点的random指针域的赋值
head = copyRandom(head);
//其实前两个方法有没有返回值都可以,因为head指针的指向一直没有改变,且是引用类型=>不论head所在的链表如何改变=>head仍然指向该指针=>可以将该链表的改变带回
//3、将复制的节点从head链表中抽离出来,并将head链表也还原成原来的样子
Node copyHead = copyDivide(head);
return copyHead;
}
//1、在链表head中完成对于该链表中所有节点的复制(不包含random指针域的复制)
public static Node copyBase(Node head){
//设置一个在head链表中的指针(指向head链表的头节点),实现对于head链表的遍历
Node list = head;
while(list != null ){
//在遍历过程中,对于head链表中的每一个不为null的节点,都要进行复制操作
//1、定义一个新节点temp;2、将list节点的信息复制给它temp;3、实现该新节点temp的插入操作;4、将list节点指向对应的head链表中的下一个节点
Node temp = new Node(list.val);//实现复制的节点
//实现对于节点插入操作必须要进行的指针的修改
temp.next = list.next;
list.next = temp;
//更新list节点
list = temp.next;
}
return head;
}
//2、在链表head中,实现对于复制节点的random指针域的赋值
public static Node copyRandom(Node head){
//定义一个指针,指向head链表的头节点,实现对于head链表的遍历
Node list = head;
//定义一个指针,指向复制出来的第一个节点(如果该节点存在的话),实现对于复制得到的节点的遍历
Node cur = null;//初值为null,是因为如果head链表为空的话,直接使用cur = head.next会出现:空指针异常
//当head节点不为空的时候(否则会出现空指针异常)
while(list != null){
cur = list.next;//定位好cur指针指向的元素
//因为list的random域,指向指向链表中的任意节点或者 null
//当list.random指向的不是null时,=>相对应的,cur的random域,指向的是:list的random域指向的节点,复制出来的对应的节点=>位于该节点list.random后面的节点
//当list.random指向的不是null时,=>cur的random域指向的也将是null(不用做出改变,因为最初的值即为null)。但应该与上面的情况分开!因为null.next会出现空指针异常!!!!!!
if(list.random != null){
cur.random = list.random.next ;
}
//实现链表的向前遍历
list = cur.next;
}
return head;
}
//3、将复制的节点从head链表中抽离出来,并将head链表也还原成原来的样子
public static Node copyDivide(Node head){
//定义一个指针,指向head链表的头节点,实现对于head链表的遍历
Node list = head;
//定义一个指针,指向复制出来的第一个节点(如果该节点存在的话),实现对于复制得到的节点的遍历
Node cur = null;//初值为null,是因为如果head链表为空的话,直接使用cur = head.next会出现:空指针异常
Node copyHead = null;//定义这个指针,指向复制之后得到的链表的头节点,最后也是return它。刚开始这个指针不能直接赋值head.next,因为,如果head指针为空的话,会出现空指针异常;不过此时正好return 它,也应该是null值,正好对应默认情况
//开始进行循环
while (list != null){//没有遍历完head链表
//先写出copyHead赋值的情况,这种情况只出现一次
if(copyHead == null){
//copyHead指向 第一个节点
copyHead = head.next;
//并且cur节点不用处理前面节点指向它的指针
cur = list.next;
}else{
//需要考虑前一个cur节点,指向此次cur节点的指针问题
cur.next = list.next;
cur = list.next;
}
//常规情况的操作(对于list指针)
list.next = cur.next;//先修改指针指向
list = list.next;//然后接着向后遍历
}
return copyHead;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
引用别人的一句话,送给自己吧!
技术的突飞猛进往往是自然发生的。 你在某个夜晚苦熬一个知识点时, 不会觉得自己突飞猛进; 只有在多年后的某日, 熟练地给别人讲解这个知识点后, 内心才会小小地波动一下, 猛然忆起当年深夜中的青灯一盏。