剑指 Offer 35. 复杂链表的复制
题目描述
构造链表的深拷贝。深拷贝应该正好由 n 个全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
解法一:哈希表
/**
* @param {Node} head
* @return {Node}
*/
var copyRandomList = function(head) {
if(!head) return null
const map = new Map()
let cur = head
while(cur){// 第一次遍历,生成一个具有val属性的链表;
map.set(cur, new Node(cur.val))
cur = cur.next
}
cur = head
while(cur){//第二次遍历,根据map映射关系,将random和next指针指向对应的节点或者null;
map.get(cur).next = map.get(cur.next) || null
map.get(cur).random = map.get(cur.random) || null
cur = cur.next
}
return map.get(head)
};
复杂度分析:
时间复杂度 O(N) 空间复杂度 O(N)
解法二:拼接 + 拆分
算法流程:
-
复制各节点,构建拼接链表:
原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> ……
-
构建新链表各节点的 random 指向:
当访问原节点 cur 的随机指向节点 cur.random 时,对应新节点 cur.next 的随机指向节点为 cur.random.next 。
-
拆分原 / 新链表:
设置 pre / cur 分别指向原 / 新链表头节点,遍历执行 pre.next = pre.next.next 和 cur.next = cur.next.next 将两链表拆分开。
-
返回新链表的头节点 res 即可。
var copyRandomList = function(head) {
if(!head) return null
let cur = head
while(cur){//复制各节点,并构建拼接链表
let node = new Node(cur.val)
node.next = cur.next
cur.next = node
cur = cur.next.next
}
cur = head
while(cur){//构建各新节点的random指向
if(cur.random){
cur.next.random = cur.random.next
}
cur = cur.next.next
}
cur = res = head.next
pre = head
while(cur.next){//拆分两链表
pre.next = pre.next.next
cur.next = cur.next.next
pre = pre.next
cur = cur.next
}
pre.next = null//单独处理原链表尾结点
return res//返回新链表头结点
};
复杂度分析:
时间复杂度 O(N) : 三轮遍历链表,使用 O(N) 时间。
空间复杂度 O(1) : 节点引用变量使用常数大小的额外空间。