【问题标题】:Copy List with Random Pointer. What is the second while loop doing here?使用随机指针复制列表。第二个while循环在这里做什么?
【发布时间】:2021-10-19 07:10:31
【问题描述】:
"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        oldToCopy = { None : None} 
        
        curr = head 
        while curr:
            copy = Node(curr.val)
            oldToCopy[curr] = copy 
            curr = curr.next 
            
        curr = head 
        while curr:
            copy = oldToCopy[curr]
            copy.next = oldToCopy[curr.next]
            copy.random = oldToCopy[curr.random]
            curr = curr.next 
            
        return oldToCopy[head]
            

此代码正在制作链接列表的深层副本。在第一个 while 循环中,我们复制每个节点值并将其放入 hashmap 中。在第二个while循环中,我们将copy设置为从hashmap中获取的值?

当我执行oldToCopy[head] 时,将copycopy.nextcopy.random 分配给哈希图中的值如何返回答案。

对这段代码在做什么感到困惑。

【问题讨论】:

    标签: python python-3.x linked-list hashmap


    【解决方案1】:

    这是一个链表,所以每个节点都有一个指向下一个节点的指针(在这种情况下还有一个指向随机节点的指针)。

    第一个循环构造oldToCopy,它是原始节点到节点副本的映射。这意味着当您在 oldToCopy 中查找某些内容时,您正在使用原始节点作为检索该节点副本的键。

    然后第二个循环再次遍历原始列表并:

    • 分配copy 指向在第一个循环中创建的节点curr 的副本(这只是为了方便避免反复使用oldToCopy[curr])。
    • copy.next 被指定为指向curr.next 指向的节点的副本
    • copy.random 的分配方式类似。

    为每个副本设置nextrandom 指针是在两个循环中完成的,因为当您复制节点x 时,您还没有x.next 指向的节点的副本 - 您'将在下一次迭代中制作该副本。

    【讨论】:

    • 我很困惑,这里给copy、copy.next和copy.random赋值有什么用?
    • 这是一个链表,所以每个节点都有一个指向下一个节点的指针(在这种情况下,它似乎也是一个指向随机节点的指针)。当您在oldToCopy 中查找某些内容时,您正在使用原始节点作为检索该节点副本的键。顺其自然,copy 被赋值为指向新创建的节点副本(这只是为了方便避免反复使用oldToCopy[curr]),copy.next 被赋值为指向副本 curr.next 指向的节点,copy.random 的分配方式类似。
    【解决方案2】:

    用一个例子来可视化这个过程可能会有所帮助:

     head          ┌──────────────────────┐
      ↓            │                      v
    ┌─────────────┐│  ┌─────────────┐   ┌─────────────┐
    │ val: 1      ││  │ val: 2      │   │ val: 3      │
    │ next: ───────│─>│ next: ─────────>│ next:None   │
    │ random: ─────┘  │ random: None│<─────── :random │
    └─────────────┘   └─────────────┘   └─────────────┘
    

    所以这个例子是一个有 3 个节点的链表。第一个有一个random 对最后一个节点的引用。最后一个节点有一个random 对中间节点的引用。中间节点没有random 引用(它是None)。

    这是第一个循环完成后的结果:

     head          ┌──────────────────────┐
      ↓            │                      v
    ┌─────────────┐│  ┌─────────────┐   ┌─────────────┐
    │ val: 1      ││  │ val: 2      │   │ val: 3      │
    │ next: ───────│─>│ next: ─────────>│ next: None  │
    │ random: ─────┘  │ random: None│<─────── :random │
    └─────────────┘   └─────────────┘   └─────────────┘
       oldToCopy         oldToCopy         oldToCopy
           │                 │                 │
           │                 │                 │
           v                 v                 v
    ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
    │ val: 1      │   │ val: 2      │   │ val: 3      │
    │ next: None  │   │ next: None  │   │ next: None  │
    │ random: None│   │ random: None│   │ random: None│
    └─────────────┘   └─────────────┘   └─────────────┘
    

    上图的一些解释,现在有很多参考:

    • 底部三个节点是使用new Node() 创建的新节点。
    • 这些新节点中的引用(即它们的nextrandom 属性)都是None,因为我们没有(也不能)为这些属性传递任何好的值。新节点的 val 属性设置正确。
    • oldToCopy 是由旧节点键入的字典。给定一个旧节点,它给出相应的新节点。所以这也用向下箭头表示。

    现在让我们逐步完成第二个循环。

    首先currcopy定义如下:

     curr
     head          ┌──────────────────────┐
      ↓            │                      v
    ┌─────────────┐│  ┌─────────────┐   ┌─────────────┐
    │ val: 1      ││  │ val: 2      │   │ val: 3      │
    │ next: ───────│─>│ next: ─────────>│ next: None  │
    │ random: ─────┘  │ random: None│<─────── :random │
    └─────────────┘   └─────────────┘   └─────────────┘
       oldToCopy         oldToCopy         oldToCopy
           │                 │                 │
           │                 │                 │
           v                 v                 v
    ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
    │ val: 1      │   │ val: 2      │   │ val: 3      │
    │ next: None  │   │ next: None  │   │ next: None  │
    │ random: None│   │ random: None│   │ random: None│
    └─────────────┘   └─────────────┘   └─────────────┘
      ↑
     copy
    

    然后我们执行copy.next = oldToCopy[curr.next]。我们注意到curr.next 是中间的旧节点。 oldToCopy[curr.next] 是它正下方的节点(新节点)。所以copy.next = oldToCopy[curr.next] 对第一个新节点的next 属性有这种影响:

     curr
     head          ┌──────────────────────┐
      ↓            │                      v
    ┌─────────────┐│  ┌─────────────┐   ┌─────────────┐
    │ val: 1      ││  │ val: 2      │   │ val: 3      │
    │ next: ───────│─>│ next: ─────────>│ next: None  │
    │ random: ─────┘  │ random: None│<─────── :random │
    └─────────────┘   └─────────────┘   └─────────────┘
       oldToCopy         oldToCopy         oldToCopy
           │                 │                 │
           │                 │                 │
           v                 v                 v
    ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
    │ val: 1      │   │ val: 2      │   │ val: 3      │
    │ next: ─────────>│ next: None  │   │ next: None  │
    │ random: None│   │ random: None│   │ random: None│
    └─────────────┘   └─────────────┘   └─────────────┘
      ↑
     copy
    

    当我们执行copy.random = oldToCopy[curr.random] 时会发生类似的事情。 curr.random 是最后一个旧节点。 oldToCopy[curr.random] 是它正下方的节点。所以copy.random = oldToCopy[curr.random] 对第一个新节点的random 属性有这种影响:

     curr
     head          ┌──────────────────────┐
      ↓            │                      v
    ┌─────────────┐│  ┌─────────────┐   ┌─────────────┐
    │ val: 1      ││  │ val: 2      │   │ val: 3      │
    │ next: ───────│─>│ next: ─────────>│ next: None  │
    │ random: ─────┘  │ random: None│<─────── :random │
    └─────────────┘   └─────────────┘   └─────────────┘
       oldToCopy         oldToCopy         oldToCopy
           │                 │                 │
           │                 │                 │
           v                 v                 v
    ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
    │ val: 1      │   │ val: 2      │   │ val: 3      │
    │ next: ─────────>│ next: None  │   │ next: None  │
    │ random: ───────┐│ random: None│   │ random: None│
    └─────────────┘  │└─────────────┘   └─────────────┘
      ↑              │                   ^
     copy            └───────────────────┘   
    

    循环的第一次迭代通过将curr 引用移动到原始列表中的下一个节点来完成。此迭代使第一个新节点的nextrandom 属性引用新节点而不是旧节点。

    下一次迭代将对第二个新节点执行相同的操作——random 曾经是并且仍然是None,但 next 将被重新连接——因此我们得到:

                   ┌──────────────────────┐
     head          │   curr               │
      ↓            │    ↓                 v
    ┌─────────────┐│  ┌─────────────┐   ┌─────────────┐
    │ val: 1      ││  │ val: 2      │   │ val: 3      │
    │ next: ───────│─>│ next: ─────────>│ next: None  │
    │ random: ─────┘  │ random: None│<─────── :random │
    └─────────────┘   └─────────────┘   └─────────────┘
       oldToCopy         oldToCopy         oldToCopy
           │                 │                 │
           │                 │                 │
           v                 v                 v
    ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
    │ val: 1      │   │ val: 2      │   │ val: 3      │
    │ next: ────────> │ next: ─────────>│ next: None  │
    │ random: ───────┐│ random: None│   │ random: None│
    └─────────────┘  │└─────────────┘   └─────────────┘
                     │  ↑                 ^
                     │ copy               │
                     └────────────────────┘
    

    最后一次迭代将对最后一个新节点做同样的事情:

     head          ┌──────────────────────┐  curr
      ↓            │                      v   ↓
    ┌─────────────┐│  ┌─────────────┐   ┌─────────────┐
    │ val: 1      ││  │ val: 2      │   │ val: 3      │
    │ next: ───────│─>│ next: ─────────>│ next: None  │
    │ random: ─────┘  │ random: None│<─────── :random │
    └─────────────┘   └─────────────┘   └─────────────┘
       oldToCopy         oldToCopy         oldToCopy
           │                 │                 │
           │                 │                 │
           v                 v                 v
    ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
    │ val: 1      │   │ val: 2      │   │ val: 3      │
    │ next: ────────> │ next: ─────────>│ next: None  │
    │ random: ───────┐│ random: None│<─────── :random │
    └─────────────┘  │└─────────────┘   └─────────────┘
                     │                    ^    ↑
                     └────────────────────┘   copy
    

    最后,函数返回oldToCopy[head],这是对克隆列表的引用,现在不再引用旧节点。

    【讨论】:

    • 我知道,在第一个循环中,我们有一个哈希图“oldToCopy”,我们分配了一个键“curr”和一个值“copy”。在第二个循环中,分配 'copy = oldToCopy[curr]' 是如何对链表进行任何更改的?我可能会误解使用 hashmap 的链表(这对我来说很困惑),但从您的解释来看,我相信通过将其设置为“copy”然后为“copy.next”更改分配一个值来修改“node.val”整个节点本身。我想我明白了~
    • copy = oldToCopy[curr] 不会对链表进行任何更改。当我们知道旧节点引用时,它只是从字典中要求提供新节点引用。因此,该字典提供了图中所示的向下箭头:您给它一个原始节点,它会将为其创建的新节点交给您。花点时间浏览一下图表,在您完全理解图表是如何从一段代码中得出的之前,不要进入下一个。
    • 我多次查看图表,现在我终于明白了。很好的解释。
    【解决方案3】:

    第一个循环只是使用现有节点的值创建新节点。 (此处未指定参考文献。

    假设一个节点 - node1 具有 val=5、next=node2 和 random=node4

    oldToCopy = {node1: new_node1}
    

    第二个循环将引用(nextrandom)分配给新创建的节点。

    由于node1s、next=node2和random=node4,新创建的new_node1也应该有相同的引用但它们必须指向新创建的节点-@ 987654331@ 和new_node4。而不是旧的(node2node4)。

    这就是这段代码对每个新创建的节点所做的事情。

    # Gets the new node/copy of curr node from dict
    copy = oldToCopy[curr]
    # set the next pointer 
    copy.next = oldToCopy[curr.next]
    # set the random pointer
    copy.random = oldToCopy[curr.random]
    # move to the next node
    curr = curr.next 
    

    【讨论】:

    • 从 hashmap 中分别设置 copy、copy.next、copy.random 等于 curr、curr.next 和 curr.random 的值对 hashmap 本身有何影响?最后,oldToCopy[head] 被返回,但 hashmap 本身并没有被触及。
    猜你喜欢
    • 2011-07-18
    • 1970-01-01
    • 2023-01-13
    • 2011-02-06
    • 1970-01-01
    • 1970-01-01
    • 2014-04-16
    相关资源
    最近更新 更多