[路飞]_程序员必刷力扣题: 复制带随机指针的链表

316 阅读2分钟

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

复制带随机指针的链表

力扣链接

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

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

val:一个表示 Node.val 的整数。 random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。 你的代码 只 接受原链表的头节点 head 作为传入参数。

提示:

  • 0 <= n <= 1000
  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。

示例 1:

e1.png

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

e2.png

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

哈希表+遍历

思路

  • 完全克隆,不能指针指到旧节点 难点:

  • 如何克隆一个节点,并且知道其原来指向的节点是哪个

这里我们用Map数据来做这道题,js中Map类型任何值(对象或者原始值) 都可以作为一个键或一个值。

为了知道原始对象中每个节点对应的一个位置信息,我们直接以源节点作为key,value值为源节点的克隆节点

这样每个源节点都可以唯一对应一个新的克隆节点,问题就得到了解决

解题:

这里我们直接遍历原链表,并且用getCloneVal函数来获取当前节点对应的,新的克隆节点

如果key值是null,直接返回null,如果不是null,则去nodeMap中查找是否存在对应节点,不存在就新建一个并且返回这个新的克隆节点。

依次遍历结束,返回nodeMap中head对对应的克隆节点就是新的完全克隆的链表了

var copyRandomList = function (head) {
    var nodeMap = new Map();
    var head1 = head;
    function getCloneVal(obj) {
      if (!obj) return null;
      if(!nodeMap.get(obj)) nodeMap.set(obj,{val:obj.val})
      return nodeMap.get(obj)
    }
    while (head1) {
      var curr = getCloneVal(head1)
      curr.next = getCloneVal(head1.next);
      curr.random = getCloneVal(head1.random);
      head1 = head1.next;
    }
    return nodeMap.get(head);
  };

哈希表+递归

思路

这里我们是使用递归的方式

  • 创建Map对象
  • 如果head节点不为空,则将head节点作为key,其克隆值为Val保存在Map中
  • 继续调用copyRandomList函数递归处理head的next指针和random指针
  • 最后返回Map对象的头结点
var copyRandomList = function(head, cachedNode = new Map()) {
    if (head === null) {
        return null;
    }
    if (!cachedNode.has(head)) {
        cachedNode.set(head, {val: head.val}), Object.assign(cachedNode.get(head), {next: copyRandomList(head.next, cachedNode), random: copyRandomList(head.random, cachedNode)})
    }
    return cachedNode.get(head);
}

迭代 + 节点拆分

思路

节点拆分的意思是我们在链表的没一个节点后面都克隆一份这个节点并且放在节点的与节点next之间

这样原来的A->B->C 就变成了 A->A'->B->B'->C->C'

思路类似于map表 map表通过key来找对对应的克隆节点 节点拆分是通过源节点的next来找到其克隆节点

这样我们完全复制源节点的操作,最后再把克隆节点单独拆分出来就可以了

  • 首先我们要新增克隆节点,将链表变长,这里我们通过遍历链表 每次步长为next.next
  • 第二次遍历复制源节点random的信息
  • 第三次遍历处理链表中的next,单独拆分出克隆链表的next

最后返回新的headNew

var copyRandomList = function(head) {
    if (head === null) {
        return null;
    }
    for (let node = head; node !== null; node = node.next.next) {
        const nodeNew = new Node(node.val, node.next, null);
        node.next = nodeNew;
    }
    for (let node = head; node !== null; node = node.next.next) {
        const nodeNew = node.next;
        nodeNew.random = (node.random !== null) ? node.random.next : null;
    }
    const headNew = head.next;
    for (let node = head; node !== null; node = node.next) {
        const nodeNew = node.next;
        node.next = node.next.next;
        nodeNew.next = (nodeNew.next !== null) ? nodeNew.next.next : null;
    }
    return headNew;
};