一日一练: 复制带随机指针的链表

115 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝

看完题目,首先要注意的就是“深拷贝”,可以先回顾一下js里对象的深拷贝是怎么样的?

    1. 遍历对象的key,判断当前属性值的类型,
    • 简单类型直接复制(赋值),
    • 对象类型(暂不考虑函数,Date等特殊对象):遍历该对象,对该对象进行拷贝(一般是递归调用deepClone方法)
    1. 假设如下场景
var oa = { aname: 'a', c: oc }
var ob = { a: oa, bname: 'b' }
var oc = { cname: 'c', b: ob }
const obj = {
  name: '要复制的对象',
  cycle: oa
}

复制obj

  • 对于name属性,直接赋值obj[name]即可;
  • cycle属性,它是一个对象,遍历他的属性进行深拷贝,发现cycle.c也是一个对象,那么对cycle.c也需要深度拷贝,然后 cycle.c.b,然后 cycle.c.b.a。。。然后死机了,如下图:

image.png

这就是深拷贝中循环引用的问题

解决方式可以用下图表示

image.png

核心就是:通过cache去缓存之前的对象,然后优先使用cache中的对象。

这道题的解法中的解决循环引用的思路跟对象深拷贝是一致的。这道题的解法也主要依靠这个:

在创建节点之前,先去缓存中查看是否已创建:

  • 已创建: 直接获取使用
  • 没有创建:创建新节点,然后加入缓存
function copyRandomList(head: Node | null): Node | null {
    if (head === null) return null
    let duplication: Node | null = null
    // 缓存
    const map = new WeakMap()
    let cur = head
    while(cur) {
        let node
        if (!(node = map.get(cur))) {
            // 创建节点之前 先去查看cache中是否存在,有的话直接使用
            // 如果不存在新建,同时设置缓存
            node = new Node(cur.val)
            map.set(cur, node) 
        }
        // 作为复制链表的头节点
        if (duplication === null){
            duplication = node
        }         
        if (cur.random) {
            // 对于random的处理一样也需要查看cache
            let random
            if (!(random = map.get(cur.random))) {                
                map.set(cur.random, (random = new Node(cur.random.val)))
            } 
            node.random = random        
        }
        if (cur.next) {
            let next
            // next节点 同理
            if (!(next = map.get(cur.next))) {
                map.set(cur.next, (next = new Node(cur.next.val)))
            } 
            node.next = next          
        }
        cur = cur.next
    }
    return duplication
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)