LeetCode 138 复制带随机指针的链表

题目描述:

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

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

例如,如果原链表中有 XY 两个节点,其中 X.random → Y 。那么在复制链表中对应的两个节点 xy ,同样有 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;
}
上一篇:leetcode刷题记录day035:138和430


下一篇:138. 复制带随机指针的链表_链表本地输入运行的问题