如何从单向链表的尾部获取第 n 个节点(一次遍历)?

Posted

技术标签:

【中文标题】如何从单向链表的尾部获取第 n 个节点(一次遍历)?【英文标题】:How would you get the nth node from the tail in a singly linked list (in one traverse)? 【发布时间】:2013-09-28 19:58:36 【问题描述】:

所以我从考试中得到了这个问题。

如何从单链表的尾部获取第 n 个节点?

每个节点都有一个值和一个下一个(指向下一个值的指针)。我们得到了这个:

getNodeFromTail(Node head, int x) 


所以我这样做的方法是通过遍历一次来找到列表的长度。然后再去获取 (length - x) 节点。所以总共有 2 次遍历。

getNodeFromTail(Node head, int x) 
    int length = 0;
    Node headdupe = head;
    while (headdupe.next != NULL) 
         headdupe = headdupe.next;
         length++;
    
    int a = length--;
    for (int y = 0; y < a; y++) 
         head = head.next;
    
    return head;

这是对的,但还有一个额外的问题是询问我们是否可以做同样的事情,但只遍历一次。考试的时候想不出来,后来想了一个办法,但又不太确定。

我可以创建一个长度为 x 的 ArrayList。然后每次我运行while循环时,我都会在数组的顶部添加一个元素,向下级联并启动数组的最后一个元素。然后当头部命中null时,返回数组[x-1]处的节点。

这是对的吗?有没有更好的解决方案?

【问题讨论】:

我不太确定您在最后一段中所说的内容,但如果我不必担心会浪费大小的东西,我会说制作一个节点指针向量并作为您进行第一次迭代,将节点指针附加到遍历的每个节点的向量。然后当你到达最后,vector[list.length-x]。这几乎违背了链表的重点,但我认为这就是问题的重点。 这个问题很可能希望您使用递归解决方案。 @iCode4Food ...或者意识到您可以将最后一个 x 头保存在内存中。 @iCode4Food 这将需要堆栈上的O(n) 额外内存,因此与将整个链表复制到数组并从后面获取xth 元素一样有效。 【参考方案1】:
    创建 2 个指向第一个节点的指针 将指针前移一个x 并排推进两个指针,直到列表中的另一个指针到达末尾。 您的指针向后指向最后一个元素 xth。

【讨论】:

所以如果 x == 1 你基本上遍历列表两次。 @iCode4Food 不过,您只需支付一次循环开销,因此这种方式仍然更快。 这取决于您对“遍历列表两次”的定义。自然,您将按照 2*N-x Next 指针来查找 N 大小的列表,在该列表中查找到最后一个元素的 x,但同时,此方法一次执行,而不是两次。【参考方案2】:

我会做以下事情:

保留一个大小为x 的循环缓冲区,并在遍历列表时将节点添加到其中。当您到达列表的末尾时,尾部的第 x 个等于循环缓冲区中的下一个条目。

在伪代码中:

Node getNodeFromTail(Node head, int x) 
  // Circular buffer with current index of of iteration.
  int[] buffer = new int[x];
  int i = 0;

  do 
    // Place the current head in its position in the buffer and increment
    // the head and the index, continuing if necessary.
    buffer[i++ % x] = head;
    head = head.next;
   while (head.next != NULL);

  // If we haven't reached x nodes, return NULL, otherwise the next item in the
  // circular buffer holds the item from x heads ago.
  return (i < x) ? NULL : buffer[++i % x];

此解决方案需要在内存中增加一个x,并且是用运行时间换取内存的经典示例。

注意:如果输入列表小于x,则未定义。

【讨论】:

他的数组长度为x,你的数组长度为x,你的前两句话我没看懂。 @GuyGreer OPs 方法在每一步中移动所有数组元素,导致O(n + x^2) 运行时间,而这种方法使用循环数组并在O(n) 中运行 我同意你的方法运行得更快,但你说的是`将额外内存的数量限制为 x. Unless I misunderstood the OP's idea he wouldn't be using more memory than x`。 @GuyGreer OP 创建一个初始容量列表x 并继续将元素推送到该数量之外,因此它将使用比x 更多的内存。 @Benoit 他“开始”最后一个元素,所以列表的大小不会超过x+1【参考方案3】:

维护2个指针, 从开始处将第一个指针提前到第 N 个节点 现在指向第二个指向头部的指针 现在继续前进两个指针,直到第一次到达终点 第二个指针现在指向倒数第 N 个

如果列表中的元素少于 N 个,请格外小心

【讨论】:

所以如果 N == 1 你基本上遍历列表两次。【参考方案4】:

你可以在不遍历两次或递归的情况下做到这一点。 请参阅以下内容:

int getNodeFromTail(Node head, int position)

    if (head == NULL)
      return 0;

      // 2 pointers needed
      Node first = head;
      Node sec = head;

      for(int i = 0; i < position; i++)
        sec = sec.next;

      while (sec.next != NULL)
      
        sec = sec.next;
        first = first.next;
      

      return first;

【讨论】:

【参考方案5】:

您不需要 2 个效率低下的循环,只需使用 2 个指针和一个计数器:

Node getNodeFromTail(Node head, int x)

    Node p = head;
    Node q = head;

    int diff = 0;

    while (p.next != NULL)
    
        p = p.next;

        if (diff >= x)
            q = q.next;
        else
            diff++;
    
    return q;

【讨论】:

【参考方案6】:

这是最简单的解决方案

static int getNodeNoStack(ListNode head, int k) 
    ListNode result = head;
    int count = 0;
    while(head.next != null) 
        if(count < k) 
            count++;
         else 
            result = result.next;
        
        head = head.next;
    
    return result.val;

您只需将指针“结果”保持在距离 head 的 k 处,遍历整个列表直到结束。一旦 head 位于末尾,则结果指针将位于 tail 的第 k 个位置

【讨论】:

以上是关于如何从单向链表的尾部获取第 n 个节点(一次遍历)?的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法面试题单向链表倒数第k个节点

数据结构与算法面试题单向链表倒数第k个节点

19. 删除链表的倒数第N个节点

剑指offer3.4-代码的鲁棒性

leetcode复盘:19. 删除链表的倒数第N个节点

算法实现思路