【经典算法】LeetCode133克隆图(Java/C/Python3实现含注释说明,中等)

简介: 【经典算法】LeetCode133克隆图(Java/C/Python3实现含注释说明,中等)

题目描述

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val(int) 和其邻居的列表(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 都是唯一的,

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

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

原题:LeetCode 133

思路及实现

方式一:深度优先搜索(DFS)

思路

对于图中的每个节点,我们可以使用深度优先搜索(DFS)来遍历图,并在遍历的过程中复制节点和边。首先,我们需要创建一个与原始图相同大小的空图来存储克隆结果。然后,我们可以从任意节点开始,递归地遍历图,并在遍历过程中复制节点和边。

在遍历过程中,我们需要使用一个哈希表来存储原始节点和克隆节点之间的映射关系,以避免重复复制节点。当我们遇到一个尚未复制的节点时,我们创建它的克隆节点,并将其添加到克隆图中。然后,我们递归地复制与该节点直接相连的所有节点,并在克隆图中添加相应的边。

代码实现

Java版本
import java.util.*;  
  
class Node {  
    public int val;  
    public List<Node> neighbors;  
      
    public Node() {  
        val = 0;  
        neighbors = new ArrayList<Node>();  
    }  
      
    public Node(int _val) {  
        val = _val;  
        neighbors = new ArrayList<Node>();  
    }  
      
    public Node(int _val, ArrayList<Node> _neighbors) {  
        val = _val;  
        neighbors = _neighbors;  
    }  
}  
  
public class Solution {  
    private Map<Node, Node> visited = new HashMap<>();  
  
    public Node cloneGraph(Node node) {  
        if (node == null) {  
            return null;  
        }  
          
        // 如果已经访问过该节点,则直接返回克隆节点  
        if (visited.containsKey(node)) {  
            return visited.get(node);  
        }  
          
        // 创建克隆节点  
        Node cloneNode = new Node(node.val);  
        visited.put(node, cloneNode); // 将原始节点和克隆节点映射关系存入哈希表  
          
        // 递归复制邻居节点,并添加边到克隆图中  
        for (Node neighbor : node.neighbors) {  
            cloneNode.neighbors.add(cloneGraph(neighbor));  
        }  
          
        return cloneNode;  
    }  
}

说明:

使用了一个visited哈希表来存储原始节点和克隆节点之间的映射关系。

递归地复制邻居节点,并在克隆图中添加相应的边。

C语言版本

C语言实现相对复杂,因为C语言没有内置的数据结构如哈希表,需要自己实现图的节点和邻接表,以及节点的复制逻辑。

#include <stdio.h>  
#include <stdlib.h>  
#include <stdbool.h>  
// 定义节点结构
struct Node {
    int val;
    int numNeighbors;
    struct Node** neighbors;
};
// 深度优先搜索函数
struct Node* cloneGraphDFS(struct Node* node, struct Node** clones) {
    // 如果节点为空,则返回空
    if (node == NULL)
        return NULL;
    // 如果节点已经克隆过,则直接返回克隆节点
    if (clones[node->val] != NULL)
        return clones[node->val];
    // 创建当前节点的克隆节点
    struct Node* cloneNode = (struct Node*)malloc(sizeof(struct Node));
    cloneNode->val = node->val;
    cloneNode->numNeighbors = node->numNeighbors;
    cloneNode->neighbors = (struct Node**)malloc(cloneNode->numNeighbors * sizeof(struct Node*));
    clones[cloneNode->val] = cloneNode;
    // 遍历当前节点的邻居节点
    for (int i = 0; i < node->numNeighbors; i++) {
        // 递归克隆邻居节点并将克隆节点添加到当前节点的克隆节点的邻居列表中
        cloneNode->neighbors[i] = cloneGraphDFS(node->neighbors[i], clones);
    }
    return cloneNode;
}
// 克隆图函数
struct Node* cloneGraph(struct Node* node) {
    // 创建一个数组用于存储已克隆的节点
    struct Node* clones[101] = { NULL };
    // 调用深度优先搜索函数
    return cloneGraphDFS(node, clones);
}

说明:

使用深度优先搜索(DFS)的方式进行图的克隆。通过递归地遍历节点以及节点的邻居节点,创建克隆节点,并将克隆节点的邻居节点也进行克隆,并添加到克隆节点的邻居列表中。使用clones数组来存储已克隆的节点,避免重复克隆。

通过该C语言版本实现的深度优先搜索,在图较大时可能会导致栈溢出。如果需要处理大型图,可以考虑使用循环和栈来替代递归,以减少栈空间的消耗。

Python3版本
class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return None
        
        visited = {}
        
        def clone(node):
            if node in visited:
                return visited[node]
            
            # 克隆当前节点
            clone_node = Node(node.val)
            visited[node] = clone_node
            
            # 遍历当前节点的邻居节点,并进行克隆
            for neighbor in node.neighbors:
                clone_node.neighbors.append(clone(neighbor))
            
            return clone_node
        
        return clone(node)

说明:

使用了一个visited字典来存储原始节点和克隆节点之间的映射关系。

递归地复制邻居节点,并在克隆图中添加相应的边。

复杂度分析

  • 时间复杂度:O(N+E),其中 N 是节点的个数,E 是边的个数。
  • 空间复杂度:O(N),使用哈希表来存储已克隆的节点。

方式二:(可选)广度优先搜索(BFS)

思路

使用广度优先搜索(BFS)遍历图,并在遍历过程中进行节点的克隆。为了避免重复克隆已经访问过的节点,可以使用一个哈希表来存储已经克隆的节点。具体实现时,可以使用一个队列来辅助进行广度优先搜索,并通过队列来顺序访问节点的邻居节点并进行克隆。

代码实现

Java版本
class Solution {
    public Node cloneGraph(Node node) {
        if (node == null)
            return null;
        // 创建HashMap用于存储已克隆的节点
        Map<Node, Node> visited = new HashMap<>();
        // 创建队列用于辅助广度优先搜索
        Queue<Node> queue = new LinkedList<>();
        // 初始节点克隆
        Node cloneNode = new Node(node.val, new ArrayList<>());
        visited.put(node, cloneNode);
        queue.offer(node);
        while (!queue.isEmpty()) {
            Node currNode = queue.poll();
            // 遍历当前节点的邻居节点
            for (Node neighbor : currNode.neighbors) {
                if (!visited.containsKey(neighbor)) {
                    // 如果邻居节点未克隆过,则进行克隆
                    visited.put(neighbor, new Node(neighbor.val, new ArrayList<>()));
                    queue.offer(neighbor);
                }
                // 将克隆的邻居节点添加到当前节点的克隆节点的邻居列表中
                visited.get(currNode).neighbors.add(visited.get(neighbor));
            }
        }
        return cloneNode;
    }
}

说明:

利用HashMap存储已克隆的节点,以避免重复克隆。

使用队列辅助进行BFS遍历,从初始节点开始,一层一层地克隆节点并处理邻居节点。

C语言版本
/** Definition for a Node. */
struct Node {
    int val;
    int numNeighbors;
    struct Node** neighbors;
};
struct Node* cloneGraph(struct Node* node) {
    if (node == NULL)
        return NULL;
    // 创建一个HashMap用于存储已克隆的节点
    struct Node* clones[101] = { NULL };
    // 创建一个队列用于辅助广度优先搜索
    Queue* queue = createQueue();
    // 创建初始节点克隆
    struct Node* cloneNode = (struct Node*)malloc(sizeof(struct Node));
    cloneNode->val = node->val;
    cloneNode->numNeighbors = node->numNeighbors;
    cloneNode->neighbors = (struct Node**)malloc(cloneNode->numNeighbors * sizeof(struct Node*));
    clones[cloneNode->val] = cloneNode;
    // 将原始节点入队
    enqueue(queue, node);
    while (!isEmpty(queue)) {
        // 出队当前节点
        struct Node* currNode = dequeue(queue);
        // 遍历当前节点的邻居节点
        for (int i = 0; i < currNode->numNeighbors; i++) {
            // 如果邻居节点未克隆过,则进行克隆
            if (clones[currNode->neighbors[i]->val] == NULL) {
                clones[currNode->neighbors[i]->val] = (struct Node*)malloc(sizeof(struct Node));
                clones[currNode->neighbors[i]->val]->val = currNode->neighbors[i]->val;
                clones[currNode->neighbors[i]->val]->numNeighbors = currNode->neighbors[i]->numNeighbors;
                clones[currNode->neighbors[i]->val]->neighbors = (struct Node**)malloc(clones[currNode->neighbors[i]->val]->numNeighbors * sizeof(struct Node*));
                enqueue(queue, currNode->neighbors[i]);
            }
            // 将克隆的邻居节点添加到当前节点的克隆节点的邻居列表中
            cloneNode->neighbors[i] = clones[currNode->neighbors[i]->val];
        }
    }
    return cloneNode;
}

说明

利用clones数组存储已克隆的节点,下标为节点值。

使用自定义的队列数据结构辅助进行BFS遍历。

Python3版本
class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return None
        # 创建字典用于存储已克隆的节点
        visited = {}
        # 创建队列用于辅助广度优先搜索
        queue = deque()
        # 初始节点克隆
        clone_node = Node(node.val)
        visited[node] = clone_node
        queue.append(node)
        while queue:
            curr_node = queue.popleft()
            # 遍历当前节点的邻居节点
            for neighbor in curr_node.neighbors:
                if neighbor not in visited:
                    # 如果邻居节点未克隆过,则进行克隆
                    visited[neighbor] = Node(neighbor.val)
                    queue.append(neighbor)
                # 将克隆的邻居节点添加到当前节点的克隆节点的邻居列表中
                visited[curr_node].neighbors.append(visited[neighbor])
        return clone_node

说明:

利用visited字典存储已克隆的节点,以避免重复克隆。

使用内置的deque数据结构实现队列,辅助进行BFS遍历。

复杂度分析

  • 时间复杂度:O(N + E),其中N是节点的个数,E是边的个数。
  • 空间复杂度:O(N),用于存储已克隆的节点和队列的空间。

总结

方式 优点 缺点 时间复杂度 空间复杂度
DFS 直观易理解 可能需要较多递归调用栈空间 O(V + E) O(V)
BFS 层次遍历 需要额外维护队列 O(V + E) O(V)

相似题目

相似题目 难度 链接
图的深度优先搜索 中等 LeetCode 200
图的广度优先搜索 中等 LeetCode 130
克隆含有随机指针的节点 困难 LeetCode 138

注意:上述相似题目中的链接是指向类似问题的LeetCode题目,而不是直接关联到克隆图问题的相似题目。对于克隆图这类特定问题,可能没有直接的相似题目,但它可以看作是图遍历问题的一个变种。

相关文章
|
4月前
|
jenkins Shell 测试技术
|
3月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
478 35
|
3月前
|
存储 算法 搜索推荐
《数据之美》:Java数据结构与算法精要
本系列深入探讨数据结构与算法的核心原理及Java实现,涵盖线性与非线性结构、常用算法分类、复杂度分析及集合框架应用,助你提升程序效率,掌握编程底层逻辑。
|
3月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
4月前
|
jenkins Java Shell
Java、Python、C++支持jenkins和SonarQube(全集)
Jenkins 是一个开源的持续集成(CI)和持续交付(CD)工具,用于自动化构建、测试和部署软件项目。它基于 Java 开发,支持跨平台运行,并拥有丰富的插件生态系统,可以灵活地扩展功能
462 1
|
存储 算法 Java
【数据结构与算法】1、学习动态数组数据结构(基本模拟实现 Java 的 ArrayList 实现增删改查)
【数据结构与算法】1、学习动态数组数据结构(基本模拟实现 Java 的 ArrayList 实现增删改查)
288 0
|
Rust 算法 安全
【算法学习】1588. 所有奇数长度子数组的和(java / c / c++ / python / go / rust)
给你一个正整数数组 arr ,请你计算所有可能的奇数长度子数组的和。 子数组 定义为原数组中的一个连续子序列。 请你返回 arr 中 所有奇数长度子数组的和 。
【算法学习】1588. 所有奇数长度子数组的和(java / c / c++ / python / go / rust)
|
Rust 算法 安全
【算法学习】剑指 Offer II 054. 所有大于等于节点的值之和|538|1038(java / c / c++ / python / go / rust)
给定一个二叉搜索树,请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 提醒一下,二叉搜索树满足下列约束条件: 节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左右子树也必须是二叉搜索树。
【算法学习】剑指 Offer II 054. 所有大于等于节点的值之和|538|1038(java / c / c++ / python / go / rust)
|
存储 Rust 算法
【算法学习】剑指 Offer II 083. 没有重复元素集合的全排列|46. 全排列(java / c / c++ / python / go / rust)
给定一个不含重复数字的整数数组 nums ,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。
【算法学习】剑指 Offer II 083. 没有重复元素集合的全排列|46. 全排列(java / c / c++ / python / go / rust)
|
Rust 算法 安全
【算法学习】剑指 Offer II 079. 所有子集|78. 子集(java / c / c++ / python / go / rust)
给定一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
【算法学习】剑指 Offer II 079. 所有子集|78. 子集(java / c / c++ / python / go / rust)