题目描述
给定一个链表,每个节点包含一个额外增加的随即指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的深拷贝 (这里可以理解为,拷贝前后的数据为完全独立的内存空间,不存在指针共用、数据共享,只是二者对应节点的val
数值相等而已)。
我们用一个由n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个[val, random_index]
表示:
val
: 一个表示Node.val
的整数。random_index
: 随即指针指向的节点索引(范围从0
到n - 1
; 如果不指向任何节点,则为null
)。
示例1:
输入: head = [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
输出: [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
图1
示例2:
输入: head = [[1, 1], [2, 1]]
输出: [[1, 1], [2, 1]]
图2
示例3:
输入: head = []
输出: []
解释: 给定的链表为空(空指针),因此返回null
。
提示:
-1000 <= node.val <= 10000
;Node.random
为空(null
)或指向链表中的节点;- 节点数目不超过
1000
。
思路分析
我们需要注意的是:
- 题干要求为深拷贝;
next
指针为链表规则排列;random
为任一排列。
我们可以通过next
指针对链表进行遍历,难点是:
random
的节点判存;random
如何进行链接;next
如何进行判存以及链接?
对于难点1:
我们可以通过一个辅助容器visitedNodeMap
,来暂存我们访问过的节点,但是由于是深拷贝,我们首先需要在原链表中判断是否访问过这个节点,我们不妨设其为oldNode
;之后在新链表中来新建节点newNode
,同时完善其后置节点newNode.next
与随机指针节点newNode.random
。
这里我们对新旧节点均进行了操作,所以我们需要设置辅助容器为一个二元数据结构,这里通常使用HashMap
,由于oldNode
为主标识,我们将key
设置为oldNode
, value
设置为 newNode
, 故这里的辅助容器初始化为:
Map<ListNode, ListNode> visitedNodeMap = new HashMap<>();
对于难点2:
random
节点的链接问题,我们需要对访问历史visitedNodeMap
先判断oldNode.random
是否存在:
- 如果存在,直接返回
visitedNodeMap.get(oldNode.random)
; - 如果不存在,新建节点
node = new ListNode(oldNode.random.val, _next = null, _random = null)
,并将这对节点放入访问历史visitedNodeMap.put(oldNode.random, node)
。
对于难点3:
其实这个问题,在难点1, 2中已经有了答案,只是将对象oldNode.random
换成了oldNode.next
, 这里我们不妨将此过程进行函数封装,功能为获取指定节点的深度拷贝:
代码1:获取给定节点的深度拷贝
Map<ListNode, ListNode> visitedNodeMap = new HashMap<>();
public ListNode getClone(ListNode node){
if(node == null) return null;
if(!visitedNodeMap.contains(node))
visitedNodeMap.put(node, new ListNode(node.val,
_next = null, _random = null));
return visitedNodeMap.get(node);
}
解题步骤
- 初始化指针,并对首节点进行深拷贝;
- 遍历原始链表,完善
next
与random
节点信息; - 从访问历史
visitedNodeMap
中返回结果visitedNodeMap.get(head)
.
解题代码
public class Leetcode138_copy_list_with_random_pointer {
static Map<ListNode, ListNode> visitedNodeMap = new HashMap<>();
static class ListNode {
int val;
ListNode next;
ListNode random;
ListNode(int x) {
this(x, null, null);
}
ListNode(int x, ListNode _next, ListNode _random) {
val = x;
next = _next;
random = _random;
}
}
public static ListNode solution(ListNode head) {
if (head == null) {
return null;
}
/* Step1:
Init. pointer
and
copy the very first node
*/
ListNode oldNode = head;
ListNode newNode = new ListNode(head.val, null, null);
visitedNodeMap.put(oldNode, newNode);
/* Step2: go through the head-list
and
complement info of 'newNode.random' and 'newNode.next'
*/
while (oldNode != null) {
newNode.random = getCloneNode(oldNode.random);
newNode.next = getCloneNode(oldNode.next);
oldNode = oldNode.next;
newNode = newNode.next;
}
/* Step3: return new deep copy list
*/
return visitedNodeMap.get(head);
}
public static ListNode getCloneNode(ListNode node) {
if(node == null) return null;
if (!visitedNodeMap.containsKey(node))
visitedNodeMap.put(node, new ListNode(node.val, null, null));
return visitedNodeMap.get(node);
}
复杂度分析
时间复杂度:我们对原始链表进行了一次遍历,容易理解时间复杂度为O(N)
;
空间复杂度:我们这里设置了辅助容器visitedNodeMap
,里面维护着2N
个链表节点,故空间复杂度为O(N)
.
GitHub源码
完整可运行文件请访问GitHub。