题目
方法一-广度优先遍历
算法思路
- 创建哈希表保存已拷贝的节点,格式为 {原节点:拷贝节点}
- 创建队列,并将头节点入队;
- 当队列非空时,弹出一个节点。若该节点的 n e x t next next 节点没有被拷贝过,则拷贝 n e x t next next 节点并加入队列;若该节点的 r a n d o m random random 节点没有被拷贝过,则拷贝 r a n d o m random random 节点并加入队列。
具体代码
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if(head == null)return null;
//哈希表存储原链表中节点和新链表中节点的对应关系
Map<Node, Node> oldNew = new HashMap<Node, Node>();
oldNew.put(head, new Node(head.val));
//广度优先遍历拷贝链表
bfs(head, oldNew);
return oldNew.get(head);
}
private void bfs(Node old, Map<Node, Node> oldNew){
while(old != null){
Node copyNode = oldNew.get(old);
//复制当前节点的 next 节点
Node oldNext = old.next;
Node copyNext = null;
if(oldNext != null){
if(!oldNew.containsKey(oldNext)){
oldNew.put(oldNext, new Node(oldNext.val));
}
copyNext = oldNew.get(oldNext);
}
//复制当前节点的 random 节点
Node oldRandom = old.random;
Node copyRandom = null;
if(oldRandom != null){
if(!oldNew.containsKey(oldRandom)){
oldNew.put(oldRandom, new Node(oldRandom.val));
}
copyRandom = oldNew.get(oldRandom);
}
//建立联系
copyNode.next = copyNext;
copyNode.random = copyRandom;
//移动到下一个节点进行拷贝
old = oldNext;
}
}
}
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),n 为链表节点总数
- 空间复杂度: O ( n ) O(n) O(n),哈希表的额外空间开销达到了 O ( n ) O(n) O(n)
方法二-深度优先遍历
算法思路
- 创建哈希表保存已拷贝的节点,格式为 {原节点:拷贝节点}
- 创建队列,并将头节点入队;
- 当队列非空时,弹出一个节点。若该节点的 n e x t next next 节点没有被拷贝过,则拷贝 n e x t next next 节点并加入队列,即,使用递归拷贝所有的 n e x t next next 节点
- 使用递归拷贝所有的 r a n d o m random random 节点。
也就是先通过 n e x t next next 指针拷贝出所有节点,然后再将节点之间的 r a n d o m random random 连接起来
具体代码
class Solution {
public Node copyRandomList(Node head) {
if(head == null)return null;
//第一次循环,用next将所有节点拷贝出来,并用next连接
Node copyHead = new Node(head.val);
Node p = head.next;//指向旧链表中下一个节点
Node q = copyHead;//指向copy链表
while(p != null){
Node node = new Node(p.val);
q.next = node;
q = q.next;
p = p.next;
}
//第二次循环,用哈希表存储原链表中节点和新链表中节点的对应关系
Map<Node, Node> oldNew = new HashMap<Node, Node>();
p = head;
q = copyHead;
while(p != null){
oldNew.put(p, q);
p = p.next;
q = q.next;
}
//第三次循环,将拷贝出的链表中的节点用random连接
p = head;
while(p != null){
if(p.random != null){
oldNew.get(p).random = oldNew.get(p.random);
}
else{
oldNew.get(p).random = null;
}
p = p.next;
}
return copyHead;
}
}
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),n 为链表节点总数
- 空间复杂度: O ( n ) O(n) O(n),哈希表的额外空间开销达到了 O ( n ) O(n) O(n)
方法三-迭代+拼接拆分
算法思路
- 在原链表每一个节点后面续接一个相同的新节点
- 为每一个新节点的 r a n d o m random random 属性赋值
- 将当前链表中的原链表节点和新节点拆分开:
将新节点连接成一个新的链表;
将原链表的节点拆出来组合成原链表 - 将原链表的最后一个节点的 n e x t next next 指针改回 null
具体代码
class Solution {
public Node copyRandomList(Node head) {
if(head == null)return null;
Node curNode = head;
//拼接新节点
while(curNode != null){
Node tmpNode = new Node(curNode.val);
tmpNode.next = curNode.next;
curNode.next = tmpNode;
curNode = tmpNode.next;
}
//为新节点的 random 属性赋值
curNode = head;
while(curNode != null){
if(curNode.random != null){
curNode.next.random = curNode.random.next;
}
curNode = curNode.next.next;
}
//拆分
curNode = head.next;//新链表的头节点
Node preNode = head;//原链表的头节点
Node res = head.next;
while(curNode.next != null){
preNode.next = preNode.next.next;
curNode.next = curNode.next.next;
preNode = preNode.next;
curNode = curNode.next;
}
//将原链表的最后一个节点的 next 指针改回指向null
preNode.next = null;
return res;
}
}
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),n 为链表节点总数
- 空间复杂度: O ( 1 ) O(1) O(1)