题目描述:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random → Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random → y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
- val:一个表示 Node.val 的整数。
- random_index:随机指针指向的节点索引(范围从 0 到 n - 1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
示例:
输入:head = [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
输出:[[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
输入:head = [[1, 1], [2, 1]]
输出:[[1, 1], [2, 1]]
输入:head = [[3, null], [3, 0], [3, null]]
输出:[[3, null], [3, 0], [3, null]]
输入:head = [ ]
输出:[ ]
个人解法:
对原链表进行遍历,按照每个节点的值创建新节点存入 ArrayList 中,然后遍历这个 ArrayList 将各个节点连接起来
编写一个计算指定节点在该链表中位置的方法,较为简单,只需要从头节点开始遍历,直到找到目标节点后返回 index 即可
再次对原链表进行遍历,计算每个节点 random 节点对应的 index 值,并根据 index 值,设置 ArrayList 中各个节点的 random 指向
思路较简单,但遍历次数过多,导致时间复杂度过大
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
Node cur = head;
ArrayList<Node> nodeList = new ArrayList<>();
while (cur != null) {
nodeList.add(new Node(cur.val));
cur = cur.next;
}
for (int i = 0; i < nodeList.size() - 1; i++) {
nodeList.get(i).next = nodeList.get(i + 1);
}
cur = head;
int index;
for (Node node : nodeList) {
if (cur.random == null) {
node.random = null;
} else {
index = getIndex(head, cur.random);
node.random = nodeList.get(index);
}
cur = cur.next;
}
return nodeList.get(0);
}
public int getIndex(Node head, Node random) {
Node cur = head;
int distance = 0;
while (cur != null) {
if (cur == random) {
return distance;
}
cur = cur.next;
distance++;
}
return -1;
}
官方解法1(回溯 + 哈希表):
以下内容转载自 LeetCode
利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行 当前节点的后继节点 和 当前节点的随机指针指向的节点 拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。
用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,检查 当前节点的后继节点 和 当前节点的随机指针指向的节点 的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。
class Solution {
Map<Node, Node> cachedNode = new HashMap<Node, Node>();
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
if (!cachedNode.containsKey(head)) {
Node headNew = new Node(head.val);
cachedNode.put(head, headNew);
headNew.next = copyRandomList(head.next);
headNew.random = copyRandomList(head.random);
}
return cachedNode.get(head);
}
}
官方解法2(迭代 + 节点拆分):
以下内容转载自 LeetCode
将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A → B → C,我们可以将其拆分为 A → A' → B → B' → C → C' 。对于任意一个原节点 S ,其拷贝节点 S' 即为其后继节点。
这样,我们可以直接找到每一个拷贝节点 S' 的随机指针应当指向的节点,即为其原节点 S 的随机指针指向的节点 T 的后继节点 T' 。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。
当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空,我们需要特别判断这种情况。
public Node copyRandomList2(Node head) {
if (head == null) {
return null;
}
for (Node node = head; node != null; node = node.next.next) {
Node nodeNew = new Node(node.val);
nodeNew.next = node.next;
node.next = nodeNew;
}
for (Node node = head; node != null; node = node.next.next) {
Node nodeNew = node.next;
nodeNew.random = (node.random != null) ? node.random.next : null;
}
Node headNew = head.next;
for (Node node = head; node != null; node = node.next) {
Node nodeNew = node.next;
node.next = node.next.next;
nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
}
return headNew;
}