一、题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
- java的拷贝 详细内容可参考当前连接
Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去。
- 浅拷贝
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
- 可以通过两种方式来实现:通过拷贝构造方法实现浅拷贝,通过重写clone()方法进行浅拷贝(通过super.clone()调用Object类中的原clone方法),
//方法一:拷贝构造方法实现浅拷贝
public Person(Person p) {
this.name=p.name;
this.age=p.age;
}
//方法二:通过重写clone()方法进行浅拷贝
Student stu2=(Student)stu1.clone();
- 深拷贝
深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间
进行了深拷贝之后,无论是什么类型的属性值的修改,都不会影响另一个对象的属性值。
实现方式:
1、通过重写clone方法来实现深拷贝:只需要为对象图的每一层的每一个对象都实现Cloneable接口并重写clone方法,最后在最顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝
2、通过对象序列化实现深拷贝:将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。
Student stu2=(Student)stu1.clone();
//通过序列化方法实现深拷贝
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(stu1);
oos.flush();
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Student stu2=(Student)ois.readObject();
二、思路分析及代码实现
方法一:HashMap
先复制链表,再复制链表中的关系。
import java.util.HashMap;
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null) return null;
RandomListNode target=new RandomListNode(pHead.label);
RandomListNode cur=pHead; //遍历map
RandomListNode newHead=target; //遍历新链表
//1、将链表中的结点复制到hashmap中
HashMap<RandomListNode,RandomListNode> map=new HashMap<>();
RandomListNode p=pHead;
while(pHead!=null){
map.put(pHead,new RandomListNode(pHead.label));
pHead=pHead.next;
}
//复制链表中的关系
while(cur!=null){
newHead.next=map.get(cur.next);
newHead.random=map.get(cur.random);
cur=cur.next;
newHead=newHead.next;
}
return target;
}
}
方法二:巧妙的HashMap
用一个 hashmap 建立新旧链表节点的对应的结点关系
迭代旧链表,从而在 hashmap 中更新新链表的 next 与 random 两个字段
import java.util.HashMap;
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null) return pHead;
RandomListNode curr=pHead;
RandomListNode cur=pHead;
HashMap<RandomListNode,RandomListNode> map=new HashMap<>();
while(cur!=null){
map.put(cur,new RandomListNode(cur.label));
cur=cur.next;
}
while(curr!=null){
if(curr.next!=null){
map.get(curr).next=map.get(curr.next);
}else
map.get(curr).next=null;
map.get(curr).random=map.get(curr.random);
curr=curr.next;
}
return map.get(pHead);
}
}