解法一:哈希法
对于数据结构复制,甭管他怎么变,你就记住最简单的方式:一个哈希表 + 两次遍历。
第一次遍历专门克隆节点,借助哈希表把原始节点和克隆节点的映射存储起来;第二次专门组装节点,照着原数据结构的样子,把克隆节点的指针组装起来。
/**
* Definition for a Node.
* type Node struct {
* Val int
* Next *Node
* Random *Node
* }
*/
func copyRandomList(head *Node) *Node {
// 创建map,映射链表上的新旧节点
memo := make(map[*Node]*Node)
// 第一次遍历,申请新节点
cur := head
for cur != nil{
if _, ok := memo[cur]; !ok{
memo[cur] = &Node{Val: cur.Val}
}
cur = cur.Next
}
// 第二次遍历,复制链表的指向关系
cur = head
for cur != nil{
if cur.Next != nil{
memo[cur].Next = memo[cur.Next]
}
if cur.Random != nil{
memo[cur].Random = memo[cur.Random]
}
cur = cur.Next
}
return memo[head]
}
- 时间复杂度 O(N) : 两轮遍历链表,使用 O(N) 时间。
- 空间复杂度 O(N) : 哈希表使用线性大小的额外空间。
解法二:原链表上拼接+拆分
考虑构建 原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> …… 的拼接链表,便可在遍历原链表的过程,访问原节点的 random 指向的同时,找到新对应新节点的 random 指向节点。
/**
* Definition for a Node.
* type Node struct {
* Val int
* Next *Node
* Random *Node
* }
*/
func copyRandomList(head *Node) *Node {
if head == nil{
return nil
}
cur := head
// 构建原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> …… 的拼接链表
for cur != nil{
tmp := &Node{
Val: cur.Val,
Next: cur.Next,
}
cur.Next = tmp
cur = tmp.Next
}
// 构建新链表各节点的random指向
cur = head
for cur != nil{
if cur.Random != nil{
cur.Next.Random = cur.Random.Next
}
cur = cur.Next.Next // 跳过中间复制的新节点
}
// 拆分链表
res := head.Next // 新链表的头节点
pre, cur := head, head.Next // 两个指针分别指向原链表和新链表的头节点
for pre.Next != nil && cur.Next != nil{ // (可省略pre的判断)
// 串接原链表
pre.Next = pre.Next.Next
pre = pre.Next
// 串接新链表
cur.Next = cur.Next.Next
cur = cur.Next
}
pre.Next = nil // 断开原链表的尾节点和复制节点的连接
return res
}
- 时间复杂度 O(N) : 三轮遍历链表,使用 O(N) 时间。
- 空间复杂度 O(1) : 仅使用了额外的指针变量。