【问题标题】:Using sentinel nodes in Linked List operations在链表操作中使用哨兵节点
【发布时间】:2020-08-31 18:32:12
【问题描述】:

我在 Leetcode 上的 Remove Linked List elements 上解决了一个问题,当时我通过提供的解决方案遇到了“哨兵/虚拟节点”的概念。在某些情况下,使用这样的节点应该会使链接列表中的操作更容易,例如空列表或具有一个元素的列表等。

问题描述是-

从具有值的整数链表中删除所有元素 值。

例子:

输入:1->2->6->3->4->5->6,val = 6 输出:1->2->3->4->5

给定的解决方案是 -

class Solution {
  public ListNode removeElements(ListNode head, int val) {
    ListNode sentinel = new ListNode(0);
    sentinel.next = head;

    ListNode prev = sentinel, curr = head;
    while (curr != null) {
      if (curr.val == val) prev.next = curr.next;
      else prev = curr;
      curr = curr.next;
    }
    return sentinel.next;
  }
}

让我们考虑这个链表 - 1 -> 1 -> 2 -> 3 -> NULL,要移除的节点值为1

因此,结果列表应该是2 -> 3 -> NULL,其中该列表的head 指向2

在这一行 - sentinel.next = head; 我们将 sentinel 作为列表中的第一个节点,它的 next 节点是“头”,就像这样 - 0 -> 1 -> 1 -> 2 -> 3 -> NULL,

  • 请注意,head 仍指向 1(列表中值为 1 的第一个节点)。

  • 在while循环开始之前,我们将prev设置为sentinel,将curr设置为head

  • 在循环期间,prevcurr 沿着列表移动,都指向列表中的其他节点。请注意,head 仍然指向第一个 1。

  • 循环之后,prev 指向列表中的最后一个节点(值为 3),而 curr 变为 null

  • 但是,head 仍然指代列表中的第一个节点(值为 1),sentinel.next 指代这个节点。因此,当我们运行 return sentinel.next -> 时,我们会获得对列表中第一个节点的引用(值为 1),其 next 属性不指向任何其他节点。所以这本身就是一个节点。

那么这如何正确工作呢?应该返回的ListNode 应该引用值为2 的节点,而不是1

这是基于我对 Java 中引用如何工作的了解。因为,当我们从这个开始时 -

    ListNode sentinel = new ListNode(0);
    sentinel.next = head;

    ListNode prev = sentinel, curr = head;

我们得到这样的东西 - [sentinel] -> [head] prev 指向 sentinelcurr 指向 head

但问题是prevcurr 在列表期间都会更改引用,而sentinelhead 不会。所以,sentinel.next 仍然指向旧的head

编辑 1

为了让我的问题更清楚,这里是screenshot of the result。我想知道的是-sentinel.next 如何引用值为2 的节点?一开始,sentinel.next 被设置为head,这在任何地方都没有改变。所以,sentinel.next 应该引用列表的原始head - 值为1 的节点。

编辑 2

我想我现在明白了。变化发生在这一行prev.next = curr.next;

因为prevsentinel 都引用同一个列表元素(在这个阶段),所以sentinel.next 设置正确。

【问题讨论】:

    标签: java data-structures linked-list reference


    【解决方案1】:
    • 我猜这可能不正确:

    当我们运行 return sentinel.next -> 我们首先得到一个引用 列表中的节点

    • prev 我猜前两个值不会移动。只有它的链接被移动到下一个节点。

    • 还有一点就是sentinel是final,不会变:

    class Solution {
        public ListNode removeElements(
            final ListNode head,
            final int val
        ) {
            final ListNode sentinel = new ListNode(0);
            sentinel.next = head;
    
            ListNode prev = sentinel;
            ListNode curr = head;
    
            while (curr != null) {
                if (curr.val == val) {
                    prev.next = curr.next;
                }
    
                else {
                    prev = curr;
                }
    
                curr = curr.next;
            }
    
            return sentinel.next;
        }
    }
    
    • 也许需要注意的是,我们也不是removing 任何值(值存在于内存中),我们只是在更改链接列表的链接。

    • 最后,我们只对 Linked List 的头部感兴趣,也就是 sentinel.next,我们不想看到 prev 在哪里。

    迭代

    class Solution {
        public ListNode removeElements(ListNode head, int val) {
            if (head == null)
                return null;
    
            head.next = removeElements(head.next, val);
            return head.val == val ? head.next : head;
        }
    }
    

    1 和 1 之间的链接还在,忘记画了。

    【讨论】:

    • 是的,prev 对于两个 1s 不会移动,但对于 23 会移动,所以当循环结束时,prev 位于 @987654336 @。我添加了一条打印语句,返回值显示为“2”。截图 - imgur.com/a/J3DkJSn
    • 但这我的问题 - sentinel.next 如何引用 2,什么时候应该引用原始的 head - 1
    猜你喜欢
    • 2021-10-11
    • 1970-01-01
    • 2014-07-27
    • 1970-01-01
    • 1970-01-01
    • 2020-01-22
    • 1970-01-01
    • 2014-10-11
    • 1970-01-01
    相关资源
    最近更新 更多