424 字 2 分钟阅读

题目

题目链接

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

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

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

返回复制链表的头节点。

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

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0n-1);如果不指向任何节点,则为 null

你的代码 接受原链表的头节点 head 作为传入参数。

示例 1:

img

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

示例 2:

img

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

img

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

提示:

  • 0 <= n <= 1000
  • -104 <= Node.val <= 104
  • Node.randomnull 或指向链表中的节点。

解法

顺序遍历复制链表是比较简单的,问题是随机指针该如何复制,链表中如果存在重复元素的话随机指针如何指向正确的节点?

1. 双指针

先构建一条新的链表,然后通过双指针确定每个节点的 random 节点在链表的位置。改解法时间复杂度比较高,不推荐。

class Solution {
    public Node copyRandomList(Node head) {
        Node dummyHead = new Node(0);
        dummyHead.next = head;
        Node newDummyHead = new Node(0);
        Node pre = newDummyHead, cur;
        while (head != null) {
            cur = new Node(head.val);
            pre.next = cur;
            head = head.next;
            pre = cur;
        }
        buildRandom(dummyHead, newDummyHead);
        return newDummyHead.next;
    }

    private void buildRandom(Node dummyHead, Node newDummyHead) {
        Node head = dummyHead.next, newHead = newDummyHead.next, left, right, tmp;
        while (head != null) {
            // 利用双指针确定 random 节点位置
            right = head.random;
            left = newDummyHead.next;
            if (right != null) {
                while (right != null) {
                    left = left.next;
                    right = right.next;
                }
                tmp = newDummyHead.next;
                while (left != null) {
                    left = left.next;
                    tmp = tmp.next;
                }
                newHead.random = tmp;
            }
            head = head.next;
            newHead = newHead.next;
        }
    }
}

2 复制节点

  1. 先在原链表中每个节点的 next 节点插入当前节点的复制节点
  2. 遍历每个节点的 random 节点,将新的复制节点的 random 改为原节点 random 节点的复制节点。
  3. 抽离所有复制节点并返回。

如下图所示:

代码如下:

class Solution {
    public Node copyRandomList(Node head) {
        Node dummyHead = new Node(0);
        dummyHead.next = head;
        Node newNode, next;
        // 在每个节点后面创建复制节点
        while (head != null) {
            newNode = new Node(head.val);
            newNode.next = head.next;
            head.next = newNode;
            head = newNode.next;
        }
        copyRandom(dummyHead.next);
        separate(dummyHead);
        return dummyHead.next;
    }

    private void copyRandom(Node head) {
        // 设置随机节点
        while (head != null) {
            if (head.random != null) {
                head.next.random = head.random.next;
            }
            head = head.next.next;
        }
    }

    private void separate(Node dummyHead) {
        Node old = dummyHead.next;
        Node cur = dummyHead;
        // 将两个链表分离
        while (old != null) {
            cur.next = old.next;
            cur = cur.next;
            old.next = cur.next;
            old = old.next;
        }
    }
}

3 借助 Map

  1. 先遍历一遍链表,将原节点作为 key,新节点作为 value。
  2. 原节点的 next 和 random 节点直接 get 就能获取到对应的新节点。

代码如下:

class Solution {
    public Node copyRandomList(Node head) {
        Node dummy = new Node(0);
        dummy.next = head;
        Map<Node, Node> map = new HashMap<>();
        while (head != null) {
            map.put(head, new Node(head.val));
            head = head.next;
        }
        head = dummy.next;
        // 设置 next 和 random 
        while (head != null) {
            Node newNode = map.get(head);
            newNode.next = map.get(head.next);
            newNode.random = map.get(head.random);
            head = head.next;
        }
        return map.get(dummy.next);
    }
}