「剑指Offer 35.复杂链表的复制」

「剑指Offer 35.复杂链表的复制」
题目描述(level 中等)

请实现 copyRandomList函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个next指针指向下一个节点,还有一个random指针指向链表中的任意节点或者null。给定带有随机指针的Node结构如下:

public class Node {
   private int val;
   private Node next;
   private Node random;
   public Node(int val) {
      this.val = val;
      this.next = null;
      this.random = null;
   }
}
示例
  • 示例1
    在这里插入图片描述
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
  • 示例2
    在这里插入图片描述
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
  • 示例3
    在这里插入图片描述
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
  • 示例4
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
思路分析(哈希表)

观察给定的Node结构发现与普通的意义上的链表结构仅仅是多了随机指针random,而单链表在遍历的过程中只能逐一而下,不能随机访问。普通链表复制时,没有问题,只要构建节点的前驱与后继节点即可。但是这里多了一个随机节点,可能是null也可能是链表中任何一个节点。前面提到了,如果这些节点可以随机访问那这个问题就很好解决了,由此可以想到,我们可以先将节点存储下来,需要的时候拿过来用即可。考虑使用hash表在首次遍历时先将待复制的节点所对应的值value保存下来,并与原链表一一对应。

代码实现(Hash表)
class Solution {
  public Node copyRandomList(Node head) {
    if (null == head) {
       return null;
    }
    Map<Node, Node> nodeNodeMap = new HashMap<>();
    Node current = head;
    //将链表的值复制构建hash表,与原链表保持一致
    while (null != current) {
      nodeNodeMap.put(current, new Node(current.val));
      current = current.next;
    }
    //处理指向关系
    current = head;
    while (null != current) {
      //注意这里构建hash表时是以原表的Node为键,而新复制的链表首次遍历时仅仅保存了值,没有指向关系
      nodeNodeMap.get(current).next = nodeNodeMap.get(current.next);
      nodeNodeMap.get(current).random = nodeNodeMap.get(current.random);
      current = current.next;
    }
    return nodeNodeMap.get(head);
  }
}

思路总结

  • 以原链表节点NodeKey,值Value为复制后的链表Node构建Hash表。
  • 首次遍历时,将节点一一保存,新复制的节点保存对应value,没有对应关系
  • 第二次遍历,通过原链表的节点Node(key)获取新节点并确定nextrandom的指向关系。
  • 从哈希表中返回新复制的链表(key即为head)。
思路分析(拼接+拆分)

哈希表的思路简单来说就是保存下了所有的节点,那么可不可以在不引入哈希表的情况下复制链表,降低空间复杂度呢?借助哈希表的思路,在链表基础上复制一份拼接,首次遍历时不处理random指向。第二次遍历时处理新链表的random随机指向。最后将两个链表进行拆分开,同时还原原链表(对原链表尾指向进行单独处理)。

代码实现(拼接+拆分)
class Solution {
  public Node copyRandomList(Node head) {
    if (head == null) return null;
    Node current = head;
    //1.复制各节点,并构建拼接链表
    while (current != null) {
      Node tmp = new Node(current.val);
      tmp.next = current.next;
      current.next = tmp;
      current = tmp.next;
    }
    //2.构建各新节点的 random 指向
    current = head;
    while (current != null) {
      if (current.random != null) {
        //这里很好理解,因为是在原链表的基础进行拼接的,新节点随机节点即是当前节点的随机节点的next节点
        current.next.random = current.random.next;
      }
      current = current.next.next;
    }
    //3.拆分两链表
    current = head.next;
    Node prev = head, res = head.next;
    while (current.next != null) {
      prev.next = prev.next.next;
      current.next = current.next.next;
      prev = prev.next;
      current = current.next;
    }
    //4.对原链表的尾节点进行还原处理
    prev.next = null; 
     //5.返回新链表头节点
    return res;     
  }
}

示意图
在这里插入图片描述
思路总结

  • 复制各节点,并构建拼接链表,构建完成后,链表的长度是原链表长度的两倍

  • 构建各新节点的 random 指向,因为是在原链表的基础进行拼接的,新节点随机节点即是当前节点的随机节点的next节点,可以理解为将整体向右移动了一位,而随机节点作为整体的一部分,自然可以得出current.next.random = current.random.next;

  • 拆分两链表

  • 对原链表的尾节点进行还原处理

  • 返回新链表头节点,得到新复制的链表

时间复杂度
  • 哈希表

时间复杂度O(N):对链表进行了两次遍历,没有嵌套,时间复杂度与链表长度有关

空间复杂度O(N):引入哈希表,空间复杂度与链表长度成线性关系

  • 拼接+拆分

时间复杂度O(N):对链表进行三次独立遍历,时间复杂度与其长度有关

空间复杂度O(1):未引入新结构,原链表中拼接,临时节点变量空间占用为常数级

链接

LeetCode

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐二狗呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值