【问题标题】:Getting the nth to last element in a linked list获取链表中倒数第 n 个元素
【发布时间】:2013-09-20 01:43:45
【问题描述】:

我们有一个大小为 L 的链表,我们想检索第 n 个到最后一个元素。

解决方案1:天真的解决方案

  • 从头到尾进行第一次遍历以计算 L
  • 第二次从头传到预期位置

解决方案2:使用2个指针p1,p2

  • p1 从头开始​​迭代,p2 不动。
  • 当 p1 和 p2 之间有 n 元素时,p2 也开始迭代
  • 当 p1 到达链表末尾时,p2 处于预期位置

两种解决方案似乎具有相同的时间复杂度(即 2L - n 对列表元素的迭代) 哪个更好?

【问题讨论】:

  • 既然你没有定义“更好”,这里甚至没有问题。
  • 人们需要了解,在许多情况下,“大 O”复杂度与实际性能仅松散相关,因为它忽略了加性因素和常数乘数。
  • 解决方案一是 O(2n) => O(n),而解决方案二是 O(n)。两者都是线性的。但解决方案二听起来确实更便宜,但实际上可能是相同数量的操作。

标签: algorithm linked-list


【解决方案1】:

将链表的长度简单地存储在 O(1) 内存中会不会便宜很多?您必须进行“第一次通过”的唯一原因是因为您不知道链表的长度。如果存储长度,则可以每次迭代 (|L|-n) 个元素并轻松检索元素。对于与 L 相比更高的 n 值,这种方式可以为您节省大量时间。例如,如果 n 等于 |L|,您可以简单地返回列表的头部而根本不进行迭代。

此方法比第一个算法使用的内存略多,因为它将长度存储在内存中,但您的第二个算法使用两个指针,而此方法只使用一个指针。如果你有第二个指针的内存,你可能有内存来存储你的链表的长度。

在纯理论中,O(|L|-n) 等价于 O(n),但是有“快”线性算法,然后有“慢”算法。这类问题的两遍算法很慢。

正如@HotLicks 在 cmets 中指出的那样,“人们需要了解,在许多情况下,‘大 O’ 复杂性与实际性能仅松散相关,因为它忽略了加性因素和常数乘数。”在这种情况下,IMO 只是选择最懒惰的方法,不要想太多。

【讨论】:

    【解决方案2】:

    如何使用 3 个指针 p、q、r 和一个计数器。

    使用 p 更新计数器遍历列表。 每 n 个节点将 r 分配给 q,将 q 分配给 p

    当您到达列表末尾时,您可以计算出多远 r 来自列表的末尾。

    你可以在不超过 O(L + n) 的时间内得到答案

    【讨论】:

      【解决方案3】:

      这两种算法都是两遍的。对于相当小的 n,第二个可能具有更好的性能,因为第二遍访问已经被第一遍缓存的内存。 (通行证是交错的。)

      一次性解决方案会将指针存储在循环缓冲区或队列中,并在到达列表末尾时返回队列的“头”。

      【讨论】:

      • 在你的一次性解决方案中,队列大小是n?如果是这样,将有 n 列表迭代 + L 入队操作 + L-n 出队操作。为什么它比前 2 个解决方案更好?
      • 您从未定义过“更好”。一些应用程序需要一次性算法。此外,循环缓冲区是一个离散对象,因此它可以在没有缓存但具有不同速度的随机存取内存的机器上工作。顺便说一句,将迭代次数与队列操作数进行比较是苹果对橙子。
      【解决方案4】:

      如果n << L,解决方案 2 通常更快,因为缓存,即包含 p1 和 p2 的内存块被复制到 CPU 缓存一次,并且在需要再次访问 RAM 之前移动指针进行一系列迭代。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-11
        • 2017-10-27
        • 2019-09-05
        • 2010-10-16
        • 2011-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多