随机链表的复制
给你一个长度为 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 作为传入参数。
图一
图二
图三
示例 1:(图一)
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:(图二)
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:(图三)
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
我的思路
双指针、递归
网上思路
看到一个深拷贝的
我的思路
双指针
var copyRandomList = function (head) {
if (!head) return null;
let current = head;
while (current) {
const newNode = new Node(current.val);
newNode.next = current.next;
current.next = newNode;
current = newNode.next;
}
current = head;
while (current) {
if (current.random) {
current.next.random = current.random.next;
}
current = current.next.next;
}
current = head;
let copiedHead = head.next;
let copiedCurrent = copiedHead;
while (current) {
current.next = copiedCurrent.next;
current = current.next;
if (current) {
copiedCurrent.next = current.next;
copiedCurrent = copiedCurrent.next;
}
}
return copiedHead;
};
讲解
- 基本情况检查:如果输入的链表头节点为空,直接返回 null。
- 复制节点并插入到原节点后面
遍历原链表,对于每一个节点,在其后面插入一个与其值相同的节点,形成一个交织链表。这样做的好处是新节点紧挨着原节点,方便后续步骤中 random 指针的复制。- 复制 random 指针
再次遍历交织链表,为每个新节点设置正确的 random 指针。由于新节点就在原节点后面,我们可以很容易地找到原节点的 random 指针所指向的节点的下一个节点,即新节点的 random 指针应该指向的节点。- 拆分链表
最后,将交织链表拆分成两个独立的链表。原链表保持不变,新链表由原链表中每个节点后面插入的节点构成。
递归
var copyRandomList = function (head) {
const map = new Map();
const cloneNode = (node) => {
if (!node) return null;
if (map.has(node)) {
return map.get(node);
}
const newNode = new Node(node.val);
map.set(node, newNode);
newNode.next = cloneNode(node.next);
newNode.random = cloneNode(node.random);
return newNode;
};
return cloneNode(head);
};
讲解
- copyRandomList 函数
创建一个哈希表 map 用于存储原节点到新节点的映射- cloneNode 函数
- 这是一个递归函数,接受一个节点作为参数。
- 如果节点为空,返回 null。
- 如果节点已经存在于哈希表中,直接返回对应的新节点。
- 否则,创建一个新节点,将其存入哈希表。
- 递归调用 cloneNode 函数来设置新节点的 next 和 random 指针。
- 返回新链表的头节点
调用 cloneNode 函数并传入原链表的头节点,返回新链表的头节点。
网上思路
var copyRandomList = function (head, map = new Map()) { //记得在入参加一个map
//特判
if (!head) return null;
//如果当前map没有要找的head,那么就让他有(新建一份)
if (!map.get(head)) {
map.set( //先设置val
head,
{ val: head.val }
);
Object.assign( //第二个作为第一个的新增参数
map.get(head), //map.get(head)得到的其实就是新的head,所以下面的next和random属性就是为新的head增加的属性
{
next: copyRandomList(head.next, map), //递归处理
random: copyRandomList(head.random, map) //递归处理
}
)
}
return map.get(head);
};
讲解
- copyRandomList 是一个递归函数,接收两个参数:head(原链表的头节点)和 map(用于存储原节点到新节点的映射关系,默认为一个新的 Map)。
- 检查哈希表
检查当前节点 head 是否已经在 map 中。如果不在,说明还没有复制该节点。- 创建新节点:
在 map 中为当前节点 head 创建一个新节点,初始只包含 val 属性。- 使用 Object.assign 设置新节点的 next 和 random 属性:
- Object.assign 用于将新属性(next 和 random)添加到新节点中。
- 通过递归调用 copyRandomList,分别处理 head.next 和 head.random,确保新节点的指针正确指向新链表中的节点。
- 最后,返回 map 中存储的当前节点对应的新节点。
注意:递归过程
当调用 copyRandomList 函数时,首先检查当前节点是否已被复制。
如果未被复制,创建新节点并递归调用处理 next 和 random 指针。
递归会继续深入,直到链表的末尾和所有随机指针都被处理完。
总结
今天这道题,我自己是双指针和递归的想法,其中递归我自己解出来了,但是双指针没解出来,最后还是看了网上的解法才写出来,最后又看到了 递归+深拷贝+Map 的解法,不由得借鉴了一番,颇有感触,明天的题目可以尝试一番。