JZ35. 复杂链表的复制

70 阅读2分钟

leetcode.cn/problems/fu…

请实现 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 。  

解题思路:

普通链表的节点定义如下:

// Definition for a Node.
class Node {
    int val;
    Node next;
    public Node(int val) {
        this.val = val;
        this.next = null;
    }
}

本题链表的节点定义如下:

// Definition for a Node.
class Node {
    int val;
    Node next, random;
    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}

image.png

image.png

class Solution {
    public Node copyRandomList(Node head) {
        Node cur = head;
        Node dum = new Node(0), pre = dum;
        while(cur != null) {
            Node node = new Node(cur.val); // 复制节点 cur
            pre.next = node;               // 新链表的 前驱节点 -> 当前节点
            // pre.random = "???";         // 新链表的 「 前驱节点 -> 当前节点 」 无法确定
            cur = cur.next;                // 遍历下一节点
            pre = node;                    // 保存当前新节点
        }
        return dum.next;
    }
}

本文介绍 「哈希表」 ,「拼接 + 拆分」 两种方法。哈希表方法比较直观;拼接 + 拆分方法的空间复杂度更低。

方法一:哈希表

利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。

算法流程

image.png

复杂度分析:

时间复杂度 O(N) : 两轮遍历链表,使用 O(N) 时间。

空间复杂度 O(N) : 哈希表 dic 使用线性大小的额外空间。

代码

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        HashMap<Node, Node> hashMap = new HashMap<>();
        Node temp = head;
        // 1. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while (temp != null) {
            Node cur = new Node(temp.val);
            hashMap.put(temp, cur);
            temp = temp.next;
        }
        temp = head;
        // 2. 构建新链表的 next 和 random 指向
        while (temp != null) {
            Node next = temp.next;
            Node random = temp.random;
            Node cur = hashMap.get(temp);
            cur.next = hashMap.get(next);
            cur.random = hashMap.get(random);
            temp = temp.next;
        }
        // 3. 返回新链表的头节点
        return hashMap.get(head);
    }
}

方法二:拼接 + 拆分

image.png

复杂度分析:

时间复杂度 O(N) : 三轮遍历链表,使用 O(N) 时间。

空间复杂度 O(1) : 节点引用变量使用常数大小的额外空间。

代码

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        Node cur = head;
        // 1. 复制各节点,并构建拼接链表
        while (cur != null) {
            Node temp = new Node(cur.val);
            temp.next = cur.next;
            cur.next = temp;
            cur = temp.next;
        }
        // 2. 构建各新节点的 random 指向
        cur = head;
        while (cur != null) {
            if (cur.random != null) {
                cur.next.random = cur.random.next;
            }
            cur = cur.next.next;
        }
        // 3. 拆分两链表
        cur = head.next;
        Node pre = head, res = head.next;
        while (cur.next != null) {
            pre.next = pre.next.next;
            cur.next = cur.next.next;
            pre = pre.next;
            cur = cur.next;
        }
        pre.next = null; // 单独处理原链表尾节点
        return res;      // 返回新链表头节点
    }
}