【问题标题】:Interview - Write a program to remove even elements面试 - 编写程序以删除偶数元素
【发布时间】:2012-06-01 18:58:58
【问题描述】:

我今天被问到这个问题,我知道答案很简单,但他让我一直坚持到最后。

问题

编写一个程序来删除存储在包含1 - 100ArrayList 中的偶数。

我只是说哇

这就是我的实现方式。

ArrayList source = new ArrayList(100);
for (int i = 1; i < 100; i++)
{
    source.Add(i);
}


for (int i = 0; i < source.Count; i++)
{
    if (Convert.ToInt32(source[i]) % 2 ==0)
    {
        source.RemoveAt(i);
    }
}

//source contains only Odd elements

转折

他问我这个给他一个方程的计算复杂度是多少。我刚刚做了并说这是与 N(输入)成正比的线性。

他说:嗯.. 所以这意味着当输入大小增加时我需要等待更长的时间才能得到结果,对吗? Yes sirr you are

为我调整它,让它Log(N) 尽可能多地尝试他说的。这部分我失败得很惨。

  • 因此,请来这里寻找正确的逻辑、答案或算法来执行此操作。

注意:他不需要 Linq,不需要额外的花里胡哨。只是简单的循环或其他逻辑来做到这一点

【问题讨论】:

  • LINQ 无济于事,它只会把那些肮脏的工作留在幕后
  • 没关系。你不能使用 LINQ 来删除东西。而且使用数组并不总是很有效。
  • 由于结果是 O(N),并且你删除了 O(N) 个元素,你不能得到比 O(N) 更好的结果。
  • 很可能他想听到的是不可能(有理由):) 或者只是想看看你的方法或思维过程。
  • 由于i 总是递增,这不会错过每第二个连续偶数项吗?

标签: c# algorithm search


【解决方案1】:

我敢说复杂度实际上是 O(N^2),因为数组中的删除是 O(N),并且可能会为每个项目调用它。

所以你有 O(N) 用于遍历数组(列表)和 O(N) 用于每次删除 => O(N) * O(N)。

由于不清楚,我将解释原因。在每一步都可能会移除一个项目(假设最坏的情况是每个项目都必须被移除)。在数组中,移除是通过移位完成的。因此,要删除第一项,我需要将以下所有 N-1 项向左移动一个位置:

1 2 3 4 5 6...
<---
2 3 4 5 6...

现在,在每次迭代中我都需要换档,所以我正在做N-1 + N-2 + ... + 1 + 0 换档,结果为(N) * (N-1) / 2(算术级数),最终复杂度为 O(N^2)。

【讨论】:

  • 我没有给你投反对票,我也不喜欢给你投反对票。但是删除是按索引,因此总是 1 而不是 O(N) 如你所说
  • @Deeptechtons:您认为这种“按索引删除”究竟是如何完成的?通过转移。假设我想删除第三项。那么第 4、5...N 项中的每一项都需要向左移动 1 个位置。
  • @Deeptechtons。请参阅 ArrayList.RemoteAt() 的文档。它被记录为 O(n):msdn.microsoft.com/en-us/library/…
  • 跟踪您将插入下一个值的索引。遍历数组。如果遇到偶数,写在索引处,增加索引。如果遇到奇数,什么也不做。你经过一次,你移动每个偶数零次或一次,你根本不移动奇数。那是 O(N) - 当然,这不是问题中要求的 O(log N)。
  • @MichaelJ.Barber 其他人都在谈论 OP 的算法。
【解决方案2】:

让我们这样想:

您正在执行的删除操作的数量是数组长度的一半(如果元素存储在数组中)。所以复杂度至少是 O(N) 。

您收到的问题让我假设您的教授希望您推理存储数字的不同方式。

通常,当您有复杂的日志时,您会使用不同的结构,例如图形或树。

我能想到的具有对数复杂性的唯一方法是将数字存储在树中(有序树、b 树......我们对此进行了详细说明),但它实际上超出了考试的限制(排序数组中的数字)。

这对你有意义吗?

【讨论】:

  • @Daniele.B 当然可以。我应该标记这个答案。但是已经投票给其他人的技术点来回答他们的问题:)
  • +1 正如你所说,严格来说这个问题没有答案;因为问题表明输入是arraylist,因为它指定了这种类型限制,任何根据有效值查找要删除的项目或填充新列表的操作仍将是O(n/2) = O(n),除非您可以通过无限数量的多个线程来节省时间
【解决方案3】:

如果您保留两个索引,一个指向当前读取位置,一个指向当前写入位置,您可以获得明显更好的性能。

int read = 0
int write = 0;

这个想法是 read 依次查看数组的每个成员; write 跟踪列表的当前末尾。当我们找到要删除的成员时,我们会向前读取,但不会写入。

for (int read = 0; read < source.Count; read++) {
  if (source[read] % 2 != 0) {
    source[write] = source[read];
    write += 1;
  }
}

然后在最后告诉 ArrayList 它的新长度是 `write' 的当前值。

这会将您从原来的 O(n^2) 降到 O(n)。

(注意:我没有测试过)

【讨论】:

  • 问题是这样的:“编写一个程序来删除存储在 ArrayList 中的包含 1 - 100 的偶数”,你误解了这个问题。
  • @SaeedAmiri 哎呀,你说得对,我把它弄反了。很好的收获。
  • @SaeedAmiri 我真的不明白问题,也不明白 lain 代码中的错误在哪里。你能给我 1-10 的输入/输出吗? (以及 lain 的“错误”代码的结果)
  • @nsinreal:点击“1 小时前编辑”中的链接,您将看到 Iain 已修复的错误。
【解决方案4】:

如果不更改数据结构或对项目在 ArrayList 中的存储方式进行一些假设,我看不出您将如何避免检查每个成员的奇偶性(因此至少 O(n) 复杂性)。也许面试官只是想让你告诉他这是不可能的。

【讨论】:

    【解决方案5】:

    如果您确实必须使用 ArrayList 并且必须主动删除条目(而不是一开始就添加它们)

    不增加 i + 1 但 i + 2 将无需检查它是否为奇数。

    for (int i = source.Count - 1 ; i > 0; i = i i 2)
    {
       source.RemoveAt(i);
    }
    

    编辑:我知道这只有在source 包含按顺序从 1 到 100 的条目时才有效。

    【讨论】:

    • 他的意思是去掉偶数,而不是偶数。即使是这样,也不会提高整体复杂性。
    • 嗯,没错,但他确实说过它包含 1-100,而且他添加它们的方式意味着它们是连续的。而且它比初始版本更“调整”。
    • @Brunner 我认为你错过了重点。如果数组包含 2、2、3、3、4、4,则需要删除第 1、2、5 和 6 个元素,它们都包含偶数。
    • @Brunner 这将减少到我建议的N/2,但后来他坚持使用log(N) :(
    • @AdamLiss 在我看来,如何添加条目取决于实现,也就是没有重复项并按顺序排列。
    【解决方案6】:

    给定解决方案的问题是它从头开始,因此每次删除项目时都必须移动整个列表:

    Initial List:       1, 2, 3, 4, 5, ..., 98, 99
                             /  /  /  ///  /
    After 1st removal:  1, 3, 4, 5, ..., 98, 99, <empty>
                                /  ///  /   /
    After 2nd removal:  1, 3, 5, ..., 98, 99, <empty>, <empty>
    

    我使用斜线来尝试显示列表在每次删除后如何变化。

    您可以通过颠倒删除顺序来降低复杂性(并消除我在 cmets 中提到的错误):

    for (int i = source.Count-1; i >= 0; --i)  {
      if (Convert.ToInt32(source[i]) % 2 == 0) {
        // No need to re-check the same element during the next iteration.
        source.RemoveAt(--i);
      }
    }
    

    【讨论】:

    • @AdamLiss 试试看,你的会导致 bug :)
    • @AdamLiss 这仍然是 O(n^2) :-)
    • @nsinreal,我认为面试官是在开玩笑,因为它不可能有 log(n),因为我们要删除 O(n) 个项目,所以它不能比 O(n) ,从不,同样在 arrayList 中它也不会比 O(n^2) 更好。
    • @Saeed Amiri:arrayList 中的 O(n) 是可能的。 Michael J. Barber 评论了如何做。以“跟踪”开头……
    • @nsinreal,Barber 的回答只是对问题的误解。
    【解决方案7】:

    如果您可能有无限的并行线程可供您使用。

    假设我们有一个包含n 元素的数组。每个元素分配一个线程。假设所有线程完美同步。

    1. 每个线程决定它的元素是偶数还是奇数。 (时间O(1)。)
    2. 确定数组中它下面有多少元素是奇数。 (时间O(log(n))。)
      1. 在第二个数组中标记 0 或 1,具体取决于您在同一索引处是偶数还是奇数。所以每一个都是那个位置的赔率。
      2. 如果您的索引是奇数,请添加前一个数字。现在每个条目都是当前块中的赔率计数 2 由您自己决定
      3. 如果你的索引mod 4是2,在下面的索引处添加值,如果是3,在下面添加答案2索引。现在,每个条目都是您自己计算当前 4 块中的赔率。
      4. 使用2**i 块继续此模式(如果您在上半部分,则添加下半部分的计数)log<sub>2</sub>(n) 次 - 现在此数组中的每个条目都是下面的赔率计数。
    3. 每个 CPU 都将其值插入到正确的插槽中。
    4. 将数组截断为合适的大小。

    我敢打赌,这样的事情是你朋友心目中的答案。

    【讨论】:

    • @Deeptechtons - 你能汇报一下你朋友说的话吗?我想知道我是否正确理解了他的作弊转折。
    • 嗨,btilly,我还没有再次见到他。明天我会来回答:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-14
    • 2019-11-04
    • 2018-09-15
    • 1970-01-01
    相关资源
    最近更新 更多