[原地修改/map] 剑指 Offer 35. 复杂链表的复制

115 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情

每日刷题 2022.08.21

题目

  • 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例

  • 示例1

image.png

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

image.png

输入: head = [[1,1],[2,1]]
输出: [[1,1],[2,1]]
  • 示例3

image.png

输入: head = [[3,null],[3,0],[3,null]]
输出: [[3,null],[3,0],[3,null]]
  • 示例4
输入: head = []
输出: []
解释: 给定的链表为空(空指针),因此返回 null。

提示

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

解题思路

  • 根据题目中描述的可知:复杂的链表中的每一个节点都多了一个random属性。需要完全的复制复杂链表,返回复制后的链表头节点。

Map映射

  • 如果不考虑random属性,就可以直接从原始链表的头节点开始依次往后拷贝一份,成为新的链表。
  • 那么此时拥有random属性,就可以使用map将原始链表的节点和新创建的链表的节点做一个映射,存储结构为key:原始链表的节点,value:新创建的链表的节点。注:map中存储的是节点的地址,因此如果节点的内容被修改,其也会跟着改变。
  • 再次遍历原始链表,根据原始链表的属性random,在map集合中获取到对应的新链表中的节点val。将当前节点的random赋值为val即可。
  • 时间复杂度:o(n),空间复杂度:o(n)

原地修改

  • 原地修改的操作,并没有对于使用map的解决方法,进行时间上的优化。而是在不使用额外的空间上做了优化。
  • 第一步:对原始链表的每一个节点都新创建一份,并连接在新创建的节点的后方。

cbefd46fa099a94e77d81e9547a6899.jpg

  • 第二步:那么由此可见,偶数一定是原始链表中的节点;奇数一定是新创建的链表中的节点。也就是说当我们在查找原始链表中的第i个节点的时候,其random属性所指向的节点的下一个节点,就是新创建的链表中的random属性所指向的节点。
  • 第三步:将原始链表和新创建的链表分开。注意:这里一定要将原始的链表变回原本的样子,否则会报错!
  • 时间复杂度:o(n),空间复杂度:o(1)

AC代码

  • map映射
/**
 * @param {Node} head
 * @return {Node}
 */
var copyRandomList = function(head) {
  // 哈希表:记录原节点和现节点的映射关系
  let map = new Map();
  if(head === null) return null;
  let root = head, dummy = new Node(), d = dummy;
  while(root != null) {
    let cur = new Node(root.val);
    // 记录原节点和现在的节点之间的映射关系
    map.set(root, cur);
    d.next = cur;
    d = d.next;
    root = root.next;
  }
  root = head, d = dummy.next;
  while(root != null) {
    let key = root.random, val = map.get(key);
    d.random = val;
    root = root.next;
    d = d.next;
  }
  return dummy.next;
};
  • 原地修改
/**
 * @param {Node} head
 * @return {Node}
 */
var copyRandomList = function(head) {
  // 原地算法
  // 1. 将新创建的链表和原始的链表连接在一起:因为原始的节点后面一个是当前的节点
  let h = head, nxt;
  if(h === null) return null;
  while(h != null) {
    let cur = new Node(h.val);
    nxt = h.next;
    h.next = cur;
    cur.next = nxt;
    h = nxt;
  }
  // 2. 查找random的时候,直接查找其后面一个元素即可
  // 偶数是原始节点,奇数是新的节点
  h = head, nxt;
  while(h != null) {
    let neww = h.random === null ? null : h.random.next;
    h.next.random = neww;
    h = h.next.next;
  }
  // 3. 将原始的链表和新的链表拆解开
  let d = new Node(0), dummy = d;
  // d = d.next;
  h = head, nxt;
  while(h != null) {
    let tt = h.next.next, mm = h.next;
    h.next = tt;
    d.next = mm;
    d = d.next;
    h = h.next;
  }
  // 将就列表恢复原样
  return dummy.next;
};