剑指Offer_#35_复杂链表的复制

剑指Offer_#35_复杂链表的复制

Contents

题目

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

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

思路分析

方法1:哈希表

  1. 构建哈希表
    沿着next指针,逐个遍历旧链表节点,克隆出一个新链表节点。向哈希表添加<旧节点,新节点>的键值对。
  2. 构造新链表
    从头节点开始,遍历旧链表节点,然后通过哈希表获得旧节点所对应的新节点,以及旧节点的next节点/random节点所对应的新节点。设置每个新节点的nextrandom

方法2:原地修改链表

  1. 在每个节点后克隆一个新节点
  2. 设置新节点的random指针,指向应该指向的位置
  3. 修改next指针,将拷贝的链表分离出来

图解

enter description here

解答

解答1:哈希表

class Solution {
    public Node copyRandomList(Node head) {
        HashMap<Node,Node> map = new HashMap<>();
        Node cur = head;
        while(cur != null){
            map.put(cur,new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        while(cur != null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}

复杂度分析

时间复杂度O(n)
空间复杂度O(n)

解答2:原地修改链表

class Solution {
    public Node copyRandomList(Node head) {
        CloneNodes(head);
        ConnectRandomNodes(head);
        return ReconnectNodes(head);
    } 
    //在每个原节点之后增加一个克隆的节点
    public void CloneNodes(Node head){
        Node cur = head;
        while(cur != null){
            Node cloned = new Node(cur.val);
            cloned.next = cur.next;
            cur.next = cloned;
            cur = cloned.next;
        }
    }
    //设置每个新节点的random指针
    public void ConnectRandomNodes(Node head){
        Node cur = head;
        while(cur != null){
            //当前节点的下一个节点是克隆节点
            Node cloned = cur.next;
            if(cur.random != null) cloned.random = cur.random.next;
            //每次迭代,指针移动两个节点
            cur = cloned.next;
        }
    }
    //将拷贝的链表拆分出来
    public Node ReconnectNodes(Node head){
        //cur指针指向旧链表的节点
        Node cur = head;
        //ERROR:必须对cloneNode和cloneHead在if之外进行初始化,否则可能不被初始化
        //clonedNode是新链表的指针
        Node clonedNode = null;
        //clonedHead是新链表头节点
        Node clonedHead = null;
        if(cur != null){
            clonedHead = cur.next;      
            clonedNode = clonedHead; 
            //cur指针先走一步,这样才方便连接克隆的节点
            cur.next = clonedNode.next;
            cur = cur.next;
        }
        //cur非null,那么cloneNode一定也非null
        while(cur != null){
            //连接到下个克隆节点
            clonedNode.next = cur.next;
            //更新cloneNode指针
            clonedNode = clonedNode.next;
            //连接到下个旧链表节点
            cur.next = clonedNode.next;
            //更新cur指针
            cur = cur.next;
        }
        return clonedHead;
    }     
}

复杂度分析

时间复杂度O(n)
空间复杂度O(1)

TIP:避免链表迭代中的空指针异常

比较容易出现的问题是空指针异常,防止这个异常的方式就是增加一个条件判断。具体举例说明:

  • 因为要访问cur.random.next,所以必须保证cur.random不是null
if(cur.random != null) cloned.random = cur.random.next;
  • 因为要访问cur.next,所以必须保证cur不是null
if(cur != null){
            clonedHead = cur.next;      
            clonedNode = clonedHead; 
            //cur指针先走一步,这样才方便连接克隆的节点
            cur.next = clonedNode.next;
            cur = cur.next;
        }

也就是说,每次些链表循环的代码,一定要注意循环过程中,*.next或者*.val之类的语句,一定要再其之前增加条件判断语句if(* != null)

原文地址:https://www.cnblogs.com/Howfars/p/13275735.html