如何从单向链表的尾部获取第 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)
额外内存,因此与将整个链表复制到数组并从后面获取x
th 元素一样有效。
【参考方案1】:
-
创建 2 个指向第一个节点的指针
将指针前移一个
x
并排推进两个指针,直到列表中的另一个指针到达末尾。
您的指针向后指向最后一个元素 x
th。
【讨论】:
所以如果 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 个节点(一次遍历)?的主要内容,如果未能解决你的问题,请参考以下文章