在O(n)时间内创建链接列表的副本
Create a duplicate copy of Linked list in O(n) time
链接列表中有两个指针,第一个指向下一个节点,另一个指向随机指针。随机指针指向LinkedList的任何节点。编写一个完整的程序来创建链表(c,c++,c#)的副本,而不更改原始列表和O(n)。
在一次采访中,有人问我这个问题,我想不出答案。我们将不胜感激。
在线性时间内复制正常链表显然是微不足道的。唯一有趣的部分是"随机"指针。假设"随机"实际上是指它指向同一链表中另一个随机选择的节点。据推测,其目的是链表的副本重新创建完全相同的结构——即"下一个"指针创建一个线性列表,并且其他指针指代相同的相对节点(例如,如果原始列表的第一个节点中的随机指针指向原始列表中的第五个节点,则重复列表中的随机指示器也将指向重复列表的第五节点
在N2的时间内这样做是相当容易的。首先正常复制列表,忽略随机指针。然后一次遍历原始列表中的一个节点,并且对于每个节点再次遍历列表,找到随机指针引用的列表中的哪个节点(即,通过next
指针遍历多少个节点,以找到与当前节点的random
指针具有相同地址的next
指针。然后遍历重复列表并反转它——找到第N个节点的地址,并将其放入当前节点的随机指针中。
这是O(N2)的原因主要是对右节点的线性搜索。为了得到O(N),这些搜索需要以恒定的复杂度而不是线性复杂度进行。
显而易见的方法是构建一个哈希表,将原始列表中每个节点的地址映射到该节点在列表中的位置。然后,我们可以构建一个数组,其中包含新列表中节点的地址。
有了这些,修复随机指针就相当容易了。首先,我们通过next
指针遍历原始列表,复制节点,并构建通过next
指针连接的新列表,但不使用随机指针。在执行此操作时,我们将每个节点的地址和位置插入哈希表,并将新列表中每个节点的位置插入数组。
当我们完成这项工作时,我们会锁定地浏览旧列表和新列表。对于旧列表中的每个节点,我们查看该节点的随机指针中的地址。我们在哈希表中查找与该地址相关的位置,然后在该位置获得新列表中节点的地址,并将其放入新列表的当前节点的随机指针中。然后我们前进到新旧列表中的下一个节点。
当我们完成后,我们丢弃/销毁哈希表和数组,因为我们的新列表现在复制了旧列表的结构,并且我们不再需要额外的数据。
方法概述
我们不会一开始就尝试创建新的链表。首先复制原始链接列表中的项目,然后将列表拆分为两个相同的列表。
详细信息
步骤1:使用下一个指针运行链表,并创建每个节点的副本。
original :: A -> B -> C -> D -> E ->null
updated :: A -> A' -> B -> B' -> C -> C' -> D -> D' ->E -> E' -> null
这可以在一个循环中轻松完成
步骤2:再次遍历列表,并修复像这样的随机指针
Node *current= start;
Node *newListNode, *random;
while (current!= null) {
// If current node has a random pointer say current = A, A.random = D;
if ( (*current)->random != null ) {
// newListNode will point to A'
newListNode = (*current)->next;
random = (*current)->random;
random = (*random)->next;
// Make A' point to D's next, which is D'
(*newListNode)->random = random;
}
// Here A'.random = D'
// Current goes to the next node in the original list => B , and not A'
current= (*newListNode)->next;
}
步骤3:将列表拆分为2个列表。你只需要在这里修正下一个指针。
Original :: A -> A' -> B -> B' -> C -> C' -> D -> D' ->E -> E' -> null
New :: A -> B -> C -> D -> E ->null
A' -> B' -> C' -> D' -> E' ->null
Node *start1 = head;
Node *start2 = (*head) ->next // Please check the boundary conditions yourself,
// I'm assuming that input was a valid list
Node *next, *traverse = start2;
while (traverse != null) {
next = (*traverse)->next;
if (next == null) {
(*traverse)->next = null;
break;
}
(*traverse)->next = (*next)->next;
traverse = next;
}
通过这种方式,您在O(n)时间内创建了原始列表的副本。您遍历列表3次,仍然是n的顺序。
澄清编辑:
正如BowieOwens所指出的,只有当"随机"指针是唯一的时,以下内容才有效
我只留下大致的答案,但请不要投赞成票,因为这肯定是错误的。
如果我没有完全错的话,你可以在不使用任何额外存储的情况下完成这项工作。
在复制旧列表时,将旧节点的random
指针存储在新节点的random
指针中
然后,将旧节点的random
指针设置为指向新节点。
这将在旧列表和新列表之间提供一个"之字形"结构。
伪码:
Node* old_node = <whatever>;
Node * new_node = new Node;
new_node->random = old_node->random;
old_node->random = new_node;
一旦你像那样复制了旧列表,你就可以重新开始,但要像这样替换random
指针,恢复旧列表中的指针,同时将新列表中的random
指针设置为相应的新节点:
Node* old_random = old_node->random->random; // old list -> new list -> old list
Node* new_random = new_node->random->random; // new list -> old list -> new list
old_node->random = old_random;
new_node->random = new_random;
它在纸上看起来好多了,但恐怕我的ASCII艺术技能达不到
此确实更改了原始列表,但它已恢复到原始状态
我想这是否允许取决于面试。
如您所知,O(n)表示线性。由于您需要线性数据结构的副本,因此不会有问题,因为您只需要迭代原始列表的节点,并且对于每个访问的节点,您将为新列表创建一个新节点。我认为这个问题没有很好的表述,因为你需要了解一些方面才能做好工作:这是一个循环执行吗?"下一个"节点是什么?节点的下一个节点还是列表的第一个节点?列表如何结束?
也许这个问题是一个技巧,用来测试你如何回答奇怪的问题,因为它非常简单:
1) 迭代到每个节点
2) 为其他链表创建新节点,并复制值。
成本总是O(n):因为您只需对整个链表进行1次迭代!!!
或者,你已经理解了他的问题上的一些错误。
- 使用C链接在函数内部创建C++模板
- C++ 创建包含链表和字符串的对象的链接列表时出错
- 创建一个棋盘格或"Interweave"两个链接列表。IE 更改两个链表的指针
- Qt creator 4.11,在应用程序输出面板中创建一个链接
- 链接错误,包括我创建的相同头文件 - C++
- 如何防止 CMake 在构建时(而不是在安装时)为共享库创建符号链接?
- 如果我们不创建一个新节点并使用指针插入数据并建立链接(在链表中)怎么办?
- 在c++中为链接列表创建复制构造函数/函数
- 获取链接 创建flyweight_pattern时出错
- 无法创建类型为无序链接列表的对象
- lib OSMesa 屏幕外上下文创建在C++中失败,但仅在静态链接时失败
- 如何在VS Code中创建C++项目并链接主,函数和标题?
- GRPC创建Google Assistant API的频道链接
- 如何链接从cmake中的add_subdirectory创建的库
- 如何使用由for循环创建的向量向量构建链接列表
- G 在创建共享对象时无法链接库
- Qt 创建器中的实用程序::转换::to_string_t中的链接错误
- 在声明节点创建链接列表时,为什么静态内存分配不起作用
- 如何在Code::Blocks中通过静态链接创建独立程序
- 垃圾回收如何处理这些由方法链接创建的静态实例