Clone Graph 图的复制 @LeetCode

本文详细介绍了如何利用BFS遍历和哈希表去重实现图的克隆过程,通过实例代码展示了具体实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路:

图的复制,基于BFS,用到了Hashtable来去重


参考:

图的遍历有两种方式,BFS和DFS

这里使用BFS来解本题,BFS需要使用queue来保存neighbors

但这里有个问题,在clone一个节点时我们需要clone它的neighbors,而邻居节点有的已经存在,有的未存在,如何进行区分?

这里我们使用Map来进行区分,Map的key值为原来的node,value为新clone的node,当发现一个node未在map中时说明这个node还未被clone,

将它clone后放入queue中处理neighbors。

使用Map的主要意义在于充当BFS中Visited数组,它也可以去环问题,例如A--B有条边,当处理完A的邻居node,然后处理B节点邻居node时发现A已经处理过了

处理就结束,不会出现死循环!

queue中放置的节点都是未处理neighbors的节点!!!!

http://www.cnblogs.com/feiling/p/3351921.html

http://leetcode.com/2012/05/clone-graph-part-i.html


package Level5;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;

import Utility.UndirectedGraphNode;


/**
Clone Graph 

Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.


OJ's undirected graph serialization:
Nodes are labeled uniquely.

We use # as a separator for each node, and , as a separator for node label and each neighbor of the node.
As an example, consider the serialized graph {0,1,2#1,2#2,2}.

The graph has a total of three nodes, and therefore contains three parts as separated by #.

First node is labeled as 0. Connect node 0 to both nodes 1 and 2.
Second node is labeled as 1. Connect node 1 to node 2.
Third node is labeled as 2. Connect node 2 to node 2 (itself), thus forming a self-cycle.
Visually, the graph looks like the following:

       1
      / \
     /   \
    0 --- 2
         / \
         \_/
Discuss


 */
public class S146 {

	public static void main(String[] args) {

	}
	
	public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
        if(node == null){
        	return null;
        }
        
        LinkedList<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>();		// BFS用的queue
        // Hashtable<node, clonedNode> 放原始node和其复制品
        Hashtable<UndirectedGraphNode, UndirectedGraphNode> ht = new Hashtable<UndirectedGraphNode, UndirectedGraphNode>();	// 去重用的ht
        UndirectedGraphNode retClone = new UndirectedGraphNode(node.label);	// 根节点的复制
    	ht.put(node, retClone);		// 把根节点和其复制品放入ht
        queue.add(node);		//添加入队列
        
        while(!queue.isEmpty()){
        	UndirectedGraphNode cur = queue.remove();		// 当前处理对象
        	UndirectedGraphNode curClone = ht.get(cur);	// 当前处理对象的复制品,必定在ht里,因为在前面的neighbor里已经被创建
        	
        	ArrayList<UndirectedGraphNode> neighbors = cur.neighbors;	// 得到当前原始对象的所有neighbor
        	for(int i=0; i<neighbors.size(); i++){		// 对每一个neighbor进行判断,因为有的neighbor已经被复制,有的没有
        		UndirectedGraphNode neighbor = neighbors.get(i);
        		if(ht.containsKey(neighbor)){		// 之前已经被复制过的neighbor
        			UndirectedGraphNode neighborClone = ht.get(neighbor);	// 就直接从ht里取出neighborClone
        			curClone.neighbors.add(neighborClone);		// 给curClone添加复制的neighbor
        		}else{	// 如果该neighbor没有被复制过,则新建neighborClone
        			UndirectedGraphNode neighborClone = new UndirectedGraphNode(neighbor.label);
        			curClone.neighbors.add(neighborClone);
        			ht.put(neighbor, neighborClone);		// 存储到ht里
        			queue.add(neighbor);		// 并且添加到队列里为了将来的遍历
        		}
        	}
        }
        
        return retClone;
    }

	
}



关键就是两点:

1 图的遍历 BFS或DFS

2 Hashtable<original node, copied node>


/**
 * Definition for undirected graph.
 * class UndirectedGraphNode {
 *     int label;
 *     List<UndirectedGraphNode> neighbors;
 *     UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); }
 * };
 */
 
import java.util.*;
public class Solution {
    public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
        if(node == null) {
            return null;
        }
        
        // Ht<original node, copied node>
        Hashtable<UndirectedGraphNode, UndirectedGraphNode> ht = new Hashtable<UndirectedGraphNode, UndirectedGraphNode>();
        LinkedList<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>();      // for BFS
        queue.add(node);
        
        UndirectedGraphNode copyHead = null;
        
        while(!queue.isEmpty()) {
            UndirectedGraphNode cur = queue.remove();
            UndirectedGraphNode copy = ht.get(cur);     // copy of current node
            if(copy == null) {
                copy = new UndirectedGraphNode(cur.label);
                List<UndirectedGraphNode> copynei = new ArrayList<UndirectedGraphNode>();   // create empty copied node neighbor
                copy.neighbors = copynei;
                ht.put(cur, copy);
            } 
            if(copyHead == null) {      // first node to return
                copyHead = copy;
            }
            
            List<UndirectedGraphNode> nei = cur.neighbors;
            List<UndirectedGraphNode> copynei = copy.neighbors;
            for(UndirectedGraphNode x : nei) {          // check with each original node of current neighbors
                UndirectedGraphNode copyX = ht.get(x);
                if(copyX == null) {     // if that original node has not got a copy, create it, add to ht and queue
                    copyX = new UndirectedGraphNode(x.label);
                    ht.put(x, copyX);
                    queue.add(x);
                } 
                copynei.add(copyX);
            }
        }
        
        return copyHead;
    }
}




public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
    if(node == null)
        return null;
    HashMap<UndirectedGraphNode, UndirectedGraphNode> map = new HashMap<UndirectedGraphNode, UndirectedGraphNode>();
    UndirectedGraphNode copy = new UndirectedGraphNode(node.label);
    map.put(node,copy);
    helper(node,map);
    return copy;
}
private void helper(UndirectedGraphNode node, HashMap<UndirectedGraphNode, UndirectedGraphNode> map)
{
    for(int i=0;i<node.neighbors.size();i++)
    { 
        UndirectedGraphNode cur = node.neighbors.get(i);
        if(!map.containsKey(cur))
        {
            UndirectedGraphNode copy = new UndirectedGraphNode(cur.label);
            map.put(cur,copy);
            helper(cur,map);
        }
        map.get(node).neighbors.add(map.get(cur));
    }
}


http://blog.youkuaiyun.com/linhuanmars/article/details/22715747

<think>好的,我现在需要处理用户关于LeetCode相关经典题目及解答的请求。首先,我得确定用户的具体需求是什么。用户提到“的经典问题或解法”,所以需要列举LeetCode上常见的题目,并提供解答思路和代码示例。 接下来,我需要回忆或查找LeetCode中属于类型的经典题目。常见的问题包括深度优先搜索(DFS)、广度优先搜索(BFS)、拓扑排序、最短路径、最小生成树、连通性问题等。根据这些分类,我应该挑选对应的题目。 比如,拓扑排序通常会涉及课程表问题,比如LeetCode 207题(课程表)。最短路径可能有岛屿数量问题(200题),或者克隆(133题)。另外,像冗余连接(684题)这样的并查集问题也属于的范畴。此外,可能的题目还有课程表II(210题),这是拓扑排序的应用;被围绕的区域(130题)涉及DFS/BFS处理连通区域。 接下来,我需要确保每个题目的解答符合用户的要求:结构清晰,分步骤解释,代码正确。同时,根据用户提供的引用内容,用户可能希望看到代码示例,比如Python或Java,但根据之前的引用,用户给出的示例中有C++和Java,但用户现在可能需要通用或Python的解答,但需要确认。 此外,用户要求生成相关问题,这需要在回答末尾添加相关问题,比如关于的其他算法、复杂度分析或应用场景的问题。 现在需要检查是否有引用的内容可以关联。用户提供的引用中有关于数组、双指针、哈希表等题目,但没有直接提到的问题。因此,可能需要独立回答,但如果有引用中的题目与相关,需要引用。例如,引用[4]中提到了“合并区间”可能涉及的结构,但不确定。可能用户提供的引用中没有的题目,因此回答可能需要独立编写。 接下来,我需要确保每个题目的解答正确,并符合格式要求:行内数学公式用$...$,独立公式用$$...$$,但可能在的问题中数学公式较少,但如果有复杂度分析,可能需要使用O(n)这样的表示,用$O(n)$。 例如,在拓扑排序的时间复杂度分析中,可能需要写成$O(V + E)$,其中V是节点数,E是边数。这样的行内公式需要使用$符号包裹。 同时,代码块需要用正确的语法,比如Python的def,或者Java的class。根据用户之前的引用,他们可能接受多种语言,但可能需要选择常用语言,如Python,因为用户提供的快速排序示例是Python的。 现在开始整理题目: 1. 课程表(LeetCode 207):拓扑排序,检测有向是否有环。 - 思路:使用拓扑排序,统计入度,BFS逐步移除入度为0的节点,最后检查是否所有节点都被处理。 - 代码示例:Python的BFS实现。 2. 克隆(LeetCode 133):深度优先或广度优先遍历,复制节点和邻居。 - 思路:使用哈希表记录已克隆的节点,递归或迭代复制每个节点。 - 代码示例:Python的DFS递归。 3. 岛屿数量(LeetCode 200):BFS/DFS遍历二维网格中的连通区域。 - 思路:遍历每个点,遇到陆地时进行DFS/BFS标记所有相连的陆地,计数加一。 - 代码示例:Python的DFS实现。 4. 冗余连接(LeetCode 684):并查集找环。 - 思路:遍历边,使用并查集检查当前边的两个节点是否已连通,若已连通则返回该边作为冗余。 - 代码示例:Python的并查集实现。 5. 被围绕的区域(LeetCode 130):DFS/BFS处理边界相连的区域。 - 思路:从边界'O'出发标记所有相连的'O',之后遍历整个矩阵,将未被标记的'O'转换为'X'。 - 代码示例:Python的BFS实现。 6. 课程表 II(LeetCode 210):拓扑排序输出顺序。 - 思路:类似207题,但需要记录拓扑排序的顺序。 - 代码示例:Python的BFS实现,记录结果数组。 每个问题需要分点说明,步骤清晰,代码正确,并符合格式要求。例如,在课程表的解答中,需要说明如何构建邻接表和入度数组,然后进行BFS。 然后,生成相关问题,比如关于其他算法(如Dijkstra)、复杂度分析、优化方法、应用场景等。 最后,确保引用标识正确添加。由于用户提供的引用中没有直接提到的问题,可能无法直接引用,但如果有相关内容需要关联的话。例如,引用[4]提到了合并区间,可能与的结构无关,因此可能不需要添加引用标识。不过根据用户要求,回答中引用的段落末尾需要添加引用标识,但如果没有引用来源,可能不需要。但根据用户给出的示例,如快速排序部分引用了[^1],而这里的回答可能需要引用用户提供的引用中的内容。需要检查用户提供的引用是否包含与相关的问题,但根据引用内容,似乎没有,所以可能无法添加引用,所以可能回答中不需要引用标识。或者用户可能希望引用提供的引用中的某些内容,比如引用[4]中的问题,但可能并不相关。所以可能回答中没有引用标识,但根据用户示例,可能需要添加。需要再检查。 例如,用户引用[2]提到了括号匹配,这可能与无关;引用[3]中的股票买卖问题属于动态规划,引用[4]中的数组操作,可能都不相关。因此,可能无法添加引用标识,所以回答中可能没有引用,但需要遵守用户的系统级指令中的第5点:生成相关问题,第6点:回答中引用的段落末尾添加引用标识。因此,可能用户提供的引用中没有相关的题目,所以回答中无法引用,但如果有的话需要添加。因此,可能回答中不会有引用标识,但需要明确是否有相关内容。 经过检查,用户提供的引用中没有的题目,因此无法添加引用标识,所以回答中可能不会出现等引用。但根据用户示例,即使没有引用,可能也需要遵循结构,所以可以忽略引用标识,或者可能用户希望引用其他问题,但此处可能无法。因此,可能回答中没有引用标识,但需要按照用户示例中的方式,如果有引用则添加,否则不添加。 综上,整理出LeetCode中的经典题目,给出解答思路和代码示例,并生成相关问题。</think>### LeetCode 经典题目及解答 以下是 LeetCode 中关于的经典问题及解答,涵盖的遍历、拓扑排序、连通性等核心算法。 --- #### 1. 课程表(LeetCode 207) **题目描述**:判断是否能够完成所有课程的学习(即检测有向是否存在环)。 **解决思路**: - 构建邻接表和入度数组。 - 使用 BFS 逐步移除入度为 0 的节点,并更新相邻节点的入度。 - 最终检查处理的节点数是否等于总课程数。 **代码示例(Python)**: ```python def canFinish(numCourses, prerequisites): adj = [[] for _ in range(numCourses)] in_degree = [0] * numCourses for course, pre in prerequisites: adj[pre].append(course) in_degree[course] += 1 queue = [i for i in range(numCourses) if in_degree[i] == 0] count = 0 while queue: node = queue.pop(0) count += 1 for neighbor in adj[node]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) return count == numCourses ``` --- #### 2. 克隆(LeetCode 133) **题目描述**:深度复制一个无向连通。 **解决思路**: - 使用哈希表记录已克隆的节点。 - 递归遍历原的每个节点,复制其邻居。 **代码示例(Python)**: ```python class Node: def __init__(self, val=0, neighbors=None): self.val = val self.neighbors = neighbors if neighbors else [] def cloneGraph(node): visited = {} def dfs(original): if not original: return None if original in visited: return visited[original] clone = Node(original.val) visited[original] = clone for neighbor in original.neighbors: clone.neighbors.append(dfs(neighbor)) return clone return dfs(node) ``` --- #### 3. 岛屿数量(LeetCode 200) **题目描述**:计算二维网格中岛屿的数量(连通区域问题)。 **解决思路**: - 遍历网格,遇到陆地(`'1'`)时进行 DFS 或 BFS,标记所有相连的陆地。 - 每完成一次遍历,岛屿数量加一。 **代码示例(Python)**: ```python def numIslands(grid): if not grid: return 0 rows, cols = len(grid), len(grid[0]) count = 0 def dfs(i, j): if i < 0 or j < 0 or i >= rows or j >= cols or grid[i][j] != '1': return grid[i][j] = '#' # 标记已访问 dfs(i+1, j) dfs(i-1, j) dfs(i, j+1) dfs(i, j-1) for i in range(rows): for j in range(cols): if grid[i][j] == '1': dfs(i, j) count += 1 return count ``` --- #### 4. 冗余连接(LeetCode 684) **题目描述**:找到无向中导致环出现的冗余边。 **解决思路**: - 使用并查集(Union-Find)检查每条边是否连接了已连通的节点。 **代码示例(Python)**: ```python def findRedundantConnection(edges): parent = list(range(len(edges) + 1)) def find(u): while parent[u] != u: parent[u] = parent[parent[u]] u = parent[u] return u def union(u, v): root_u, root_v = find(u), find(v) if root_u == root_v: return False parent[root_v] = root_u return True for u, v in edges: if not union(u, v): return [u, v] return [] ``` --- #### 5. 被围绕的区域(LeetCode 130) **题目描述**:将所有被 `'X'` 围绕的 `'O'` 转换为 `'X'`。 **解决思路**: - 从边界上的 `'O'` 出发,标记所有与之连通的 `'O'`。 - 最后遍历整个矩阵,将未被标记的 `'O'` 转换为 `'X'`。 **代码示例(Python)**: ```python def solve(board): if not board: return rows, cols = len(board), len(board[0]) def dfs(i, j): if i < 0 or j < 0 or i >= rows or j >= cols or board[i][j] != 'O': return board[i][j] = '#' # 标记边界连通的'O' dfs(i+1, j) dfs(i-1, j) dfs(i, j+1) dfs(i, j-1) # 处理边界行 for i in [0, rows-1]: for j in range(cols): dfs(i, j) # 处理边界列 for j in [0, cols-1]: for i in range(rows): dfs(i, j) # 转换结果 for i in range(rows): for j in range(cols): if board[i][j] == 'O': board[i][j] = 'X' elif board[i][j] == '#': board[i][j] = 'O' ``` --- #### 6. 课程表 II(LeetCode 210) **题目描述**:输出完成所有课程的合法学习顺序(拓扑排序)。 **解决思路**: - 类似课程表问题,但需要记录拓扑排序的顺序。 **代码示例(Python)**: ```python def findOrder(numCourses, prerequisites): adj = [[] for _ in range(numCourses)] in_degree = [0] * numCourses for course, pre in prerequisites: adj[pre].append(course) in_degree[course] += 1 queue = [i for i in range(numCourses) if in_degree[i] == 0] result = [] while queue: node = queue.pop(0) result.append(node) for neighbor in adj[node]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) return result if len(result) == numCourses else [] ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值