leetcode
leetcode 101 ~ 150
克隆图

克隆图

难度:

标签:

题目描述

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 valint) 和其邻居的列表(list[Node])。

class Node {
    public int val;
    public List<Node> neighbors;
}

 

测试用例格式:

简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。

邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。

给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。

 

示例 1:

输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。

示例 2:

输入:adjList = [[]]
输出:[[]]
解释:输入包含一个空列表。该图仅仅只有一个值为 1 的节点,它没有任何邻居。

示例 3:

输入:adjList = []
输出:[]
解释:这个图是空的,它不含任何节点。

示例 4:

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

 

提示:

  1. 节点数不超过 100 。
  2. 每个节点值 Node.val 都是唯一的,1 <= Node.val <= 100
  3. 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
  4. 由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
  5. 图是连通图,你可以从给定节点访问到所有节点。

代码结果

运行时间: 23 ms, 内存: 16.4 MB


/*
 * Problem Description:
 * You are given a reference of a node in a connected undirected graph, 
 * and you need to return a deep copy (clone) of the graph.
 * Each node contains a value (int) and a list of its neighbors.
 * The graph is represented as an adjacency list in the test cases.
 */
 
import java.util.*;
import java.util.stream.*;
 
class Node {
    public int val;
    public List<Node> neighbors;
    public Node(int val) {
        this.val = val;
        this.neighbors = new ArrayList<>();
    }
}
 
public class Solution {
    // HashMap to store the mapping from original node to its clone
    private Map<Node, Node> visited = new HashMap<>();
    
    // Method to clone the graph using Java Stream
    public Node cloneGraph(Node node) {
        if (node == null) return null;
        // If the node is already cloned, return the clone
        if (visited.containsKey(node)) return visited.get(node);
        // Create a new node for the clone
        Node cloneNode = new Node(node.val);
        // Store the clone in the visited map
        visited.put(node, cloneNode);
        // Clone neighbors using Stream API
        cloneNode.neighbors = node.neighbors.stream()
                                  .map(this::cloneGraph)
                                  .collect(Collectors.toList());
        return cloneNode;
    }
}

解释

方法:

这个题解使用了广度优先搜索(BFS)的思路来克隆图。首先判断给定的节点是否为空,如果为空则直接返回None。然后使用一个字典records来记录原始节点和克隆节点之间的映射关系,避免重复创建节点。定义一个辅助函数get_new_node(u),用于根据原始节点u创建对应的克隆节点,并将其存储在records中。接下来,从给定的节点开始进行BFS遍历,使用一个队列q存储待遍历的节点,并用一个布尔数组visited标记节点是否已被访问过。在BFS的过程中,对于每个遍历到的节点u,通过get_new_node(u)获取其对应的克隆节点new_u,然后遍历u的所有邻居节点v,通过get_new_node(v)获取v的克隆节点并将其添加到new_u的邻居列表中。如果v尚未被访问过,则将其标记为已访问,并将其加入队列q中等待遍历。最后,返回给定节点对应的克隆节点作为结果。

时间复杂度:

O(n+m),最坏情况下为O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在开始BFS之前需要检查给定节点是否为空?
在编程中,进行空值检查是一种常见的做法,旨在防止运行时错误。在图的克隆问题中,如果给定的节点为空,那么没有任何节点可以克隆,因此函数应该返回None。这不仅防止了后续代码对空值进行操作导致的错误,也正确地反映了输入数据的状态,即一个空图没有任何内容可被克隆。
🦆
在get_new_node函数中,为什么选择使用节点的值作为字典records的键,而不是使用节点对象本身?
在这种情况下,使用节点的值作为键来创建映射主要是因为值是唯一的且易于比较的标识符。使用节点对象本身作为键虽然理论上可行,但在实际操作中可能会更复杂,因为需要确保对象的唯一性和一致性。此外,使用值作为键可以更直观地访问和检查映射关系,尤其是在调试和维护代码时。
🦆
在BFS过程中,每个节点的所有邻居都被添加到克隆节点的邻居列表中,这个过程是否保证了邻居之间的正确连接关系,即原图中的任意两个相邻节点在克隆图中也相邻?
是的,该过程确保了原图中任意两个相邻节点在克隆图中也将相邻。通过对每个节点使用get_new_node函数来获取或创建克隆节点,并将这些克隆节点添加到对应的克隆邻居列表中,保证了克隆图中节点间的连接关系与原图一致。通过这样的方法,任何在原图中相邻的两个节点,其对应的克隆节点也会在克隆图中成为邻居。
🦆
visited数组的大小固定为101,这是否意味着解决方案只适用于节点数量不超过100的图?如果是,该如何修改代码以支持更大的图?
是的,当前代码中visited数组固定为101,意味着它只能处理节点值从0到100的图。如果需要处理更大的图,可以考虑以下几种方法:1. 如果节点数和节点值范围是已知的,可以将visited数组的大小调整为节点值的最大范围加一。2. 如果节点值的范围未知或非常大,可以使用哈希表(例如Python中的字典)来代替数组,以节点值为键,访问状态为值,这样可以灵活地处理任意范围的节点值。

相关问题

随机链表的复制

给你一个长度为 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 作为传入参数。

 

示例 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]]

 

提示:

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