在最近的一次采访中,我被问到:
从第一个位置开始,找到未知长度的排序列表的中间元素。
我回答:
有2个位置计数器:
C1的 计数器2
将计数器1递增1,将计数器2递增2.当计数器2到达列表计数器1的末尾时,计数器1将位于中间。我觉得这样做效率不高,因为我正在重新审视我已经看过的节点。无论哪种方式,是否有更高效的算法?
答案 0 :(得分:9)
假设有一个链表,你可以在任意接近N个项目的同时进行。
做5/4 N:
当你点击列表的末尾时,前一个锚点位于中点之前,但至少已经有一半了。所以N代表完整的迭代+最多1/4 N代表锚= 5/4 N。
更频繁地丢弃锚点,例如在每个第1.5项的上限功率下,根据需要使你接近N(以追踪更多锚点为代价;但对于任何给定的X作为动力步骤,渐近记忆是不变的。
答案 1 :(得分:3)
我假设您正在讨论链接列表。确实,您的解决方案非常出色。另一种方法是简单地遍历列出元素数量的列表,然后从头开始,遍历计算量的一半。两种方法最终遍历3n/2
个节点,因此没有太大区别。
根据体系结构的不同,任何一种方法都可能存在轻微的缓存优势;第一种方法可能具有缓存节点的优势,如果缓存足够大,则在两个指针相距两个远之前,这可能意味着更快的检索。或者,如果我们一次遍历列表,缓存可能会获得更好的块,而不是保持两个指针活着。
答案 2 :(得分:2)
假设您可以检测到您已超出列表的末尾并在列表中有效地寻找任意位置,您可以将列表的假定长度加倍(猜测长度为1,然后是2,然后是4, ...)直到你超过列表的末尾,然后在最后尝试的值小于列表长度和超出列表末尾的第一个值之间使用二进制搜索来查找实际的结束名单。
然后,寻找位置END_OF_LIST / 2。
这样,您就不必访问每个节点。
答案 3 :(得分:0)
从技术上讲,如果您使用O(N)内存(假设您正在使用链接列表),则可以在一次遍历中执行此操作。
编辑:我确实喜欢你的答案!
答案 4 :(得分:0)
假设常规内存链接列表允许读取下一个元素,给出当前多次引用(免责声明,未经测试,但该想法应该有效):
// assume non-empty list
slow = fast = first;
count = 0;
while (fast)
{
fast = fast.next;
if (!fast)
break;
count++;
fast = fast.next;
if (fast)
count++;
slow = slow.next;
}
if (count % 2)
return slow.data;
else
return (slow.data + slow.next.data)/2.0;
更难的情况是“列表”不是内存中的链表,而是一个可以按排序顺序读取的流,以及只读取一次的每个元素的内容,我没有一个很好的解决方案。