目录
题目
方法1:错误的方法(初尝试)
方法2:复制、拆开
方法3:哈希表
总结
题目
请实现 copyRandomList
函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next
指针指向下一个节点,还有一个 random
指针指向链表中的任意节点或者 null
。
题目地址:剑指 Offer 35. 复杂链表的复制 - 力扣(LeetCode)
或同题:138. 复制带随机指针的链表 - 力扣(LeetCode)
方法1:错误的方法(初尝试)
思路
1.先通过递归 创建新链表,每个节点的val 和 next 与旧的链表对应关系相同
2.再通过 原链表中每个节点的random的val 来找到新链表中每个节点的random的指向
这种方法是错误的方法(可以直接去看后面的方法),只是作者刚开始的尝试,错误的原因在于:每个节点的val不是唯一的,这样的话,你第2步用val来复制每个节点的random是不可以的,有些示例会过不去。
例如
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
那么第1步的结果是:newNode = [[7,null],[13,null],[11,null],[10,null],[1,null]]
然后 第2步就是对每个节点的 random进行指向。
代码
class Solution {
public Node copyRandomList(Node head) {
//复制节点的val、next
Node newNode = copy(head);
Node p1 = head;
Node p2 = newNode;
//复制节点的 random
while (p1 != null) {
if (p1.random != null) {
int val = p1.random.val;
p2.random = findNode(val,newNode);
}
p1 = p1.next;
p2 = p2.next;
}
return newNode;
}
/**
* 复制 Node 的 val 和 next
*/
Node copy(Node oldNode) {
if (oldNode == null) return null;
Node newNode = new Node(oldNode.val);
newNode.next = copy(oldNode.next);
return newNode;
}
/**
* 找到某个节点:他属于head,并且val 是 val
*/
Node findNode(int val,Node head){
if (head == null)return null;
Node p = head;
while (p!=null){
if (p.val == val)return p;
p = p.next;
}
return null;
}
}
方法2:复制、拆开
思路来自
作者:王尼玛 链接:https://leetcode.cn/problems/copy-list-with-random-pointer/solutions/295083/liang-chong-shi-xian-tu-jie-138-fu-zhi-dai-sui-ji-/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
第一步,根据遍历到的原节点创建对应的新节点,每个新创建的节点是在原节点后面,比如下图中原节点1不再指向原原节点2,而是指向新节点1 第二步是最关键的一步,用来设置新链表的随机指针
上图中,我们可以观察到这么一个规律
原节点1的随机指针指向原节点3,新节点1的随机指针指向的是原节点3的next
原节点3的随机指针指向原节点2,新节点3的随机指针指向的是原节点2的next
也就是,原节点i的随机指针(如果有的话),指向的是原节点j
那么新节点i的随机指针,指向的是原节点j的next
第三步就简单了,只要将两个链表分离开,再返回新链表就可以了
代码
class Solution {
public Node copyRandomList(Node head) {
if(head==null) {
return null;
}
Node p = head;
//第一步,在每个原节点后面创建一个新节点
//1->1'->2->2'->3->3'
while(p!=null) {
Node newNode = new Node(p.val);
newNode.next = p.next;
p.next = newNode;
p = newNode.next;
}
p = head;
//第二步,设置新节点的随机节点
while(p!=null) {
if(p.random!=null) {
p.next.random = p.random.next;
}
p = p.next.next;
}
//第三步,将两个链表分离(注意这里是分离,不能修改原来的链表)
Node res = new Node(-1);
Node oldNode = head;
Node newNode = res;
while (oldNode!=null){
newNode.next = oldNode.next;
newNode = newNode.next;
oldNode.next = newNode.next;
oldNode = oldNode.next;
}
return res.next;
}
}
方法3:哈希表
思路
思路来自
作者:王尼玛 链接:https://leetcode.cn/problems/copy-list-with-random-pointer/solutions/295083/liang-chong-shi-xian-tu-jie-138-fu-zhi-dai-sui-ji-/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
我们用哈希表来解决这个问题
首先创建一个哈希表,再遍历原链表,遍历的同时再不断创建新节点
我们将原节点作为key,新节点作为value放入哈希表中
第二步我们再遍历原链表,这次我们要将新链表的next和random指针给设置上
从上图中我们可以发现,原节点和新节点是一一对应的关系,所以
- map.get(原节点),得到的就是对应的新节点
- map.get(原节点.next),得到的就是对应的新节点.next
- map.get(原节点.random),得到的就是对应的新节点.random
所以,我们只需要再次遍历原链表,然后设置:
新节点.next -> map.get(原节点.next)
新节点.random -> map.get(原节点.random)
这样新链表的next和random都被串联起来了
最后,我们然后map.get(head),也就是对应的新链表的头节点,就可以解决此问题了。
代码
class Solution {
public Node copyRandomList(Node head) {
if(head==null) {
return null;
}
//创建一个哈希表,key是原节点,value是新节点
Map<Node,Node> map = new HashMap<Node,Node>();
Node p = head;
//将原节点和新节点放入哈希表中
while(p!=null) {
Node newNode = new Node(p.val);
map.put(p,newNode);
p = p.next;
}
p = head;
//遍历原链表,设置新节点的next和random
while(p!=null) {
Node newNode = map.get(p);
//p是原节点,map.get(p)是对应的新节点,p.next是原节点的下一个
//map.get(p.next)是原节点下一个对应的新节点
if(p.next!=null) {
newNode.next = map.get(p.next);
}
//p.random是原节点随机指向
//map.get(p.random)是原节点随机指向 对应的新节点
if(p.random!=null) {
newNode.random = map.get(p.random);
}
p = p.next;
}
//返回头结点,即原节点对应的value(新节点)
return map.get(head);
}
}
总结
最好的方法我觉得还是方法3,这个方法不仅思路简单,代码也简单。方法2虽然思路简单,但是写代码不好写,所以要多去想哈希表,原来哈希表的key 和 value 可以分别存放两个链表,所以以后看到复杂链表的复制,要去想用哈希表来复制。