【算法】 克隆图

122 阅读4分钟

难度:中等

题目

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

图中的每个节点都包含它的值 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 = []
输出:[]
解释:这个图是空的,它不含任何节点。

提示:

  • 这张图中的节点数在 [0, 100] 之间。

  • 1 <= Node.val <= 100

  • 每个节点值 Node.val 都是唯一的,

  • 图中没有重复的边,也没有自环。

  • 图是连通图,你可以从给定节点访问到所有节点。

解题思路:

使用深度优先搜索(DFS)来解决图的克隆问题。其主要思路是递归地遍历图中的每个节点,并在遍历过程中创建节点的克隆版本。为了避免重复克隆相同的节点,使用了一个 Map 结构 visited 来存储已经克隆过的节点,以节点的引用为键,克隆后的节点为值。

  1. 初始化:创建一个 Map 叫做 visited,用于存储已经克隆过的节点。如果图中存在循环,或者有多个节点指向同一个节点,这个 Map 可以防止无限循环和重复克隆。

  2. 定义 DFS 函数dfs(originalNode) 函数负责克隆 originalNode 及其所有邻居节点。

  3. 检查是否已克隆:在 dfs 函数内部,首先检查 originalNode 是否已经在 visited 中。如果是,直接返回对应的克隆节点,避免重复工作。

  4. 创建克隆节点:如果 originalNode 尚未被克隆,创建一个新的 Node 实例,将其值设置为 originalNode.val,并将邻居列表初始化为空数组。

  5. 存储克隆节点:将 originalNode 和其克隆版本的映射存储在 visited 中。

  6. 克隆邻居:遍历 originalNode 的所有邻居,对每个邻居调用 dfs 函数来获取其克隆版本,并将这些克隆版本添加到当前克隆节点的邻居列表中。

  7. 返回克隆节点:最后,dfs 函数返回当前克隆节点的引用。

  8. 启动克隆过程:在 cloneGraph 函数外部,调用 dfs(node) 来开始克隆过程。node 是要克隆的图的起始节点。

关键点:

  • 使用 Map 来跟踪已经克隆的节点,防止重复克隆和无限循环。
  • 深度优先搜索(DFS)递归地访问图中的每个节点,确保每个节点及其邻居都被克隆。
  • 每个克隆节点的邻居列表是在克隆过程中动态构建的,确保了克隆图的结构与原图完全一致。

这种方法的时间复杂度为 O(V+E),其中 V 是节点数,E 是边数。这是因为每个节点和每条边都会被访问一次。空间复杂度也为 O(V+E),主要是由于递归调用栈和 visited Map 的使用。

JavaScript实现:

/**
 * // Definition for a _Node.
 * function _Node(val, neighbors) {
 *    this.val = val === undefined ? 0 : val;
 *    this.neighbors = neighbors === undefined ? [] : neighbors;
 * };
 */

/**
 * Clone an undirected graph using DFS.
 * @param {Node} node - The starting node of the graph to be cloned.
 * @return {Node} - The cloned starting node of the new graph.
 */
var cloneGraph = function(node) {
    if (node === null) return null;
    const visited = new Map();
    
    function dfs(originalNode) {
        if (visited.has(originalNode)) {
            return visited.get(originalNode);
        }
        
        const cloneNode = new Node(originalNode.val, []);
        visited.set(originalNode, cloneNode);
        
        for (const neighbor of originalNode.neighbors) {
            cloneNode.neighbors.push(dfs(neighbor));
        }
        
        return cloneNode;
    }
    
    return dfs(node);
};