133 Clone Graph
//错误代码
class Solution {
public:
Node* cloneGraph(Node* node) {
//邻接表、BFS---》类似于二叉树的层次遍历
if(!node || !node->val) return node;
//构造队列
queue<Node*> prev;
prev.push(node);
//构造新的图结点列表
vector<Node*> adjList;
//辅助数组
// 哈希集合用于记录访问过的节点
unordered_set<Node*> visited;
visited.insert(node);
while(prev.empty()){
//获取结点并出栈
Node* newNode = new Node();
Node* curr = prev.front();prev.pop();
newNode->val = curr->val;
for(Node* neighbor : curr->neighbors){
//复制对应的邻接点
newNode->neighbors.push_back(neighbor);
// 如果邻居没有被访问过
if (visited.find(neighbor) == visited.end()) {
prev.push(neighbor); // 将未访问的邻居加入队列
visited.insert(neighbor); // 标记为已访问
}
}
adjList.push_back(newNode);
}
return adjList[0];
}
};)
- 在克隆图的过程中,直接使用了原图中的邻居列表(newNode->neighbors.push_back(neighbor)),而没有克隆邻居节点的副本。实际上,需要复制整个图中的节点及其连接关系,因此应为每个节点创建新的副本,而不是直接使用原图的邻居节点。
- 图的克隆和链表的复制(特别是带随机指针的链表的深度复制138) 很类似。两者都涉及节点的复制,并且不仅仅是单独复制节点本身,还必须考虑其邻接点或其他关联节点(例如,图的邻居节点或链表的随机指针)。
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> neighbors;
Node() {
val = 0;
neighbors = vector<Node*>();
}
Node(int _val) {
val = _val;
neighbors = vector<Node*>();
}
Node(int _val, vector<Node*> _neighbors) {
val = _val;
neighbors = _neighbors;
}
};
*/
class Solution {
public:
Node* cloneGraph(Node* node) {
if(!node) return nullptr;
//原结点-->克隆结点
unordered_map<Node* , Node*> cloneNodes;
//BFS
queue<Node*> prev;
prev.push(node);
//第一个克隆结点
cloneNodes[node] = new Node(node->val);
while(!prev.empty()){
Node* curr = prev.front();
prev.pop();
for(Node* neighbor : curr->neighbors){
//如果邻居结点还没有被克隆
if(cloneNodes.find(neighbor) == cloneNodes.end()){
cloneNodes[neighbor] = new Node(neighbor->val);
//加入队列
prev.push(neighbor);
}
//将克隆的邻居节点加入当前克隆节点的邻接表
cloneNodes[curr]->neighbors.push_back(cloneNodes[neighbor]);
}
}
return cloneNodes[node];
}
};
138 随机链表的复制
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(!head) return nullptr;
//建立新链表
Node* p = head; //p是原链表指针
Node* dum = new Node(0);
Node* temp = dum; //temp是新链表指针
//构造哈希表
unordered_map<Node* , Node*> copyList;
//第一次遍历原链表,复制next指针
while(p){
Node* node = new Node(p->val);
temp->next = node;
copyList[p] = node;
temp = temp->next;
p = p->next;
}
//第二次遍历原链表,复制random
p = head;
temp = dum->next;
while(p){
if(p->random != nullptr){
temp->random = copyList[p->random];
}
temp = temp->next;
p = p->next;
}
return dum->next;
}
};
summary 哈希表与克隆:用于存储原节点和新节点的映射
1. 防止重复克隆
当我们遍历图或链表时,如果不使用哈希表,就可能会多次创建同一个节点的副本,从而浪费内存和时间。哈希表通过记录原节点和对应的新节点,确保每个节点只会被克隆一次。每当需要访问一个节点的邻居(在图中)或者 next
和 random
(在链表中)时,哈希表可以直接提供已克隆的节点,而无需重复创建。
2. 保证边的复制
当你遍历一个节点时,你不仅需要复制该节点,还需要复制它所有的边(即图中的邻接点或者链表中的指针)。这里哈希表起到了关键作用:
- 对于图的克隆,当你克隆一个节点时,它的邻居节点可能已经被克隆,也可能还没有。如果没有克隆,哈希表会创建并存储这个邻居节点的克隆版;如果已经克隆,你可以通过哈希表找到对应的克隆节点,并将它加入当前节点的邻接表中,确保边的复制。
- 对于链表,类似地,你需要通过哈希表将
random
和next
指针正确指向克隆后的节点,而不是指向原链表中的节点。
3. 流程示例
以图为例,当我们遍历到一个节点 ( A ) 时,如果节点 ( A ) 的邻居 ( B ) 还未被克隆,哈希表就会克隆 ( B ) 并将其存储。接下来,无论你何时再次遇到 ( B ),都可以直接从哈希表中获取克隆节点。这样,边 ( A - B ) 也就得以正确复制。
直观的解释:
通过哈希表的存储和查找机制,可以保证每次访问节点时,邻居的指向都是已经克隆的副本,而不是原图中的节点,从而保证边(指针)的完整性。