每日一题之随机链表的复制及踩坑过程

题目:

image-20240720132145739

题目链接:

随机链表的复制

题解:

方法一:哈希表

需要两次遍历链表,第一次链表将复制的节点被复制的节点通过map结构一一对应起来,这时候只需要复制节点的val。第二次链表遍历,通过map结构,可以很快找到next和random对应的节点。

var copyRandomList = function(head) {
    if(!head){
        return head
    }
    let cur = head
    let map = new Map()
    // 第一次遍历,生成一个具有val属性的链表
    while(cur){
        map.set(cur,new _Node(cur.val,null,null))
        cur = cur.next
    }
​
    cur = head
    //第二次遍历,根据map映射关系,将random和next指针指向对应的节点或者null;
    while(cur){
        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)
    
};

方法二:节点拆分

我们首先将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A→B→C,我们可以将其拆分为 A→A ′ →B→B ′ →C→C ′。对于任意一个原节点 S,其拷贝节点 S ′即为其后继节点。

这样,我们可以直接找到每一个拷贝节点 S ′的随机指针应当指向的节点,即为其原节点 S 的随机指针指向的节点 T 的后继节点 T ′。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。

当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空,我们需要特别判断这种情况。

代码如下:

var copyRandomList = function(head) {
    if(!head){
        return head
    }
    let cur = head
    
    //遍历链表,每一步都生成克隆结点,插在原结点中间
    while(cur){
        let newNode = new _Node(cur.val,cur.next,null)
        cur.next = newNode
        cur = cur.next.next
    }
​
    cur = head
    //再遍历一次,`一对一对`地设置克隆结点的 random: `原结点`的 random 的 next, 就是`克隆结点`的 random 结点
    while(cur){
        let next = cur.next
        next.random = (cur.random == null ? null: cur.random.next)
        cur = cur.next.next
    }
​
    cur = head
    // 记录复制后的链表的头结点
    head = head.next
    //用一个新指针指向克隆结点的头,把原结点的 next 和克隆结点的 next 拆开,返回新指针
    while(cur){
        let next = cur.next
        cur.next = cur.next.next
        next.next = (next.next !== null) ? next.next.next :null
        cur = cur.next
    }
    
    return head
};

踩坑:

这里我想着把后面两次循环合并成一个循环,我的思路:将复制节点的next和random找到后就直接将原链表和复制链表的一部分复原,这样就不需要再开一层循环去复原链表。

代码如下:

var copyRandomList = function(head) {
    if(!head){
        return head
    }
    let cur = head
    while(cur){
        let newNode = new _Node(cur.val,cur.next,null)
        cur.next = newNode
        cur = cur.next.next
    }
​
    cur = head
    head = head.next
    // 错误思路:将两次循环做的工作放在一个循环里面
    while(cur){
        let next = cur.next
        next.random = (cur.random == null ? null: cur.random.next)
        //复原原节点
        cur.next  = next.next
        cur = cur.next
        // 复原复制节点
        next.next = (next.next == null ? null: next.next.next)
        //本来我们可以通过,cur.random.next去查找复制节点random对应的节点
        //这种复原导致我们无法再通过cur.random.next去查找复制节点random对应的节点了,因为cur.random.next指向的是下一个原节点而不是复制节点
    }
    
    return head
};

但其实是不行的,前面遍历过的部分链表复原的话会断掉前面部分的原节点与复制节点的联系(即无法通过原节点找到对应的复制节点),这样后面的节点的random可能指向前面的节点,你前面的节点如果复原的话,就无法通过原节点找到其对应的复制节点了,就会导致random的指向是错误的

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值