【问题标题】:Check whether the number is in array or not with minimum complexity检查数字是否在数组中,复杂度最小
【发布时间】:2013-07-16 12:16:58
【问题描述】:

给定一个数组,任意两个后继元素之间的距离为 1(+1 或 -1)。我们得到了一个数字。我们如何以最小的复杂度检查数字是否在数组中。

【问题讨论】:

  • 保持你的数组排序,然后你可以通过使用二进制搜索来实现在 O(log n) 内的搜索。
  • 好问题。你的尝试是什么? (澄清一下:我们很乐意为您提供帮助,但我们不会为您做作业。)
  • @cli_hlt 对于一个查询,排序将比简单的线性搜索花费更多的时间。
  • 一个很好的测验问题。一个可怕的面试问题,IMO。
  • 缺乏解决方案的尝试会导致投反对票。

标签: algorithm language-agnostic


【解决方案1】:

您可以进行某种二进制搜索。

如果我们要查找的元素介于我们知道该元素出现在数组中的第一个和最后一个元素之间,我们可以停止。

如果不是,请检查数组中可能出现的最小值和最大值,方法是找到第一个元素和最后一个元素之间的差,减去元素的数量,将其除以 2,然后从 / 中减去 / 添加两者中的最小值/最大值。

更明确:

temp = abs(arr[left] - arr[right]) - (left - right)
minPossible = min(arr[left], arr[right]) - 2*temp
maxPossible = max(arr[left], arr[right]) + 2*temp

递归重复,将数组分成两半(或拆分as Daniel suggested)。

为什么上面给出了可能的最小值/最大值:

可以这样想:你需要一些元素的数量等于左右之差才能从一个到另一个。除此之外,对于您下/上的每一步,您都需要再次上/下。

不幸的是,最坏的情况不小于 O(n)。

这就是为什么不能击败 O(n) 最坏情况的原因:

(类似于David's proof)。

示例输入 = [1,0,1,0,1,0,1,0]

假设我们正在寻找 2。

显然不存在,但是如果将其中一个 0 更改为 2 会怎样?我们需要检查每个零。一旦我们跳过一个,那一个就可能是 2。

因此我们必须检查至少 1/2 的元素,因此仍然是 O(n/2) = O(n)。

【讨论】:

  • 它没有被否决。它也很聪明——它甚至可能比 Daniel 的解决方案更有效。
  • 它看起来确实比我的解决方案更有效,因为它更多地利用了可用信息。我不再认为我的答案是最佳解决方案。我将编辑它的那一部分,但为了完整性而保留它。
  • 我最坏的情况也检查了一半的数字。在一般情况下,我仍然不知道我们是平局还是你赢了。不过,在以图形方式描绘问题之后,我认为你的算法会赢。
【解决方案2】:

您可以使用以下算法省去检查一些(可能是大多数)元素。

如果你的数字是 85 并且数组的第一个数字是 100,你可以跳过 (15-1) = 14 个数字(当然 15 是 100 和 85 之间的距离),因为它们最接近 85 是99, 98, 97, ..., 86。所以你只需检查第 15 个数字。如果该数字不是 85,请继续重复相同的算法。这让您可以跳过数组,这仍然是 O(N),但在时钟时间上可能比逐个检查要快。

最坏的情况是:我正在寻找 85。

  • 第一个数字是 86。我不能跳过任何数字,因为 (1-1) = 0,下一个数字很可能是 85。
  • 我检查下一个号码。是 87。啊,现在我可以跳过 一个 数字,因为 (2-1) = 1;我跳过的下一个数字可能是 88 或 86,但绝不是 85。
  • 我检查了other下一个数字,它是86。
  • 一切都一样,因为数组实际上是 86、87、86、87、86、87……所以我最终检查了所有 87,这几乎是数字的一半。

在阅读this answer 之前,我认为这是最佳算法。

【讨论】:

  • 几乎完美。只能通过给出精确的概率性能分析来改进。但这很难。 ;-)
  • 我认为这很聪明。但为什么还是 O(n)?它不是——从统计学上讲——不那么复杂吗? O(n) 仅在您必须检查每个元素时才适用,还是我错了?
  • @Lars Ebert:你错了。 O(n) 并不意味着您检查每个元素;这意味着,在最坏的情况下,您最多检查多个与n 成比例的元素(即n 的常数倍数)。
  • @Jason 好吧。我不知道。正如我所说,我不是数学家。但在这种情况下,我同意最小复杂度是 O(n) 并且不能降低。
  • 它是 O(n),只是 O(更小的 n) :-)
【解决方案3】:

这个问题预计细胞探测复杂度为 Ω(n),因此不会发生次线性算法。考虑可能的输入

210101010...10
012101010...10
010121010...10
...
010101010...12

概率相等。找到 2。根据 Yao 引理,对于固定输入分布,最佳随机算法并不比最佳确定性算法好。在找到 2 之前,所有未被排除的输入看起来都相同。因此,每个正确的确定性算法都必须以某种顺序探测 0 个位置,并探测预期的 n/4 个(或大约)位置,直到找到 2 个。

【讨论】:

  • 我不确定 Yao 的引理在这里是否相关:它给出了 随机 算法的下限,而不是确定性算法。我认为您以错误的方式应用它(即,您试图通过考虑随机算法必须处理的分布来推断确定性算法的下限)。
  • @KonradRudolph 是的,我正在降低随机算法的性能,其中确定性算法是其子类。 Yao 的原则是,如果在不考虑算法的情况下选择输入分布,那么在不失一般性的情况下考虑确定性算法的行为就足够了。
【解决方案4】:

当然复杂度不能小于O(N)。那就是说我会遍历整个数组。

无论何时

  • 读取值减去所需数字的绝对值大于 OR 后面的元素数
  • 读取值等于所需值

我会“破坏”算法,回答“是”。

默认情况下,答案是“否”! :)

【讨论】:

  • 这不是最佳的,因为您已经找到了它们之间的实际数字。
  • 是的,但您仍然需要检查所有值以确认它不存在!
  • 实际上,如果数组中不包含数字,复杂度可以小于O(N)!如果您检查第一个值并看到它与相关数字的距离大于数组的长度,则您已经证明该数字不在数组内。我不是数学家,所以其他人必须计算它的复杂性,对不起!
  • @LarsEbert 好点!!!但是,复杂度仍然是 O(N),您不会根据特殊情况计算复杂度:这是最坏情况分析。这就像说如果数组已经排序,那么对向量进行排序可能是 O(N)(因为您只需要检查它是否是)。
  • @Lars 您可以通过少于 N 次的比较而侥幸——但少于 O(N)?没有。
【解决方案5】:

假设您在数组中查找值v。如果在给定位置i 的值a[i]v-d,那么我们正在寻找的值与i 至少相距d 个单元格。所以你可以跳过中间的单元格,因为最多可以有 v-d+1v-d+2 等值。所以这是一个草率的递归算法:

template <class ForwardIter, class T>
ForwardIter find_in_singlestep_range(ForwardIter first, ForwardIter last, T val) {
  if (first == last) return last;
  if (*first == val) return first;

  auto max_diff = last-first;
  auto diff = make_signed<T>{val} - make_signed<T>{*first};
  if (diff < 0) diff = -diff;
  if (diff > max_diff) return last;

  return find_in_singlestep_range(first+diff, last, val);
}

复杂性: 您最多将获得 1/2*N +1 比较,因为假设您从未遇到过该元素,最坏的情况可能是 diff 为 1,2,2,2,2,因此您跳过每个第二个元素。 (如果 diff 为 2,则下一个 diff 只能是 2 或 4。)

如果进行二分搜索,可能会更复杂:如果子数组的中间元素与val 的差异大于子数组大小的一半,则可以跳过子数组中的搜索。

【讨论】:

    【解决方案6】:

    由于元素之间的差值为 1(+1 或 -1),因此 min(array)max(array) 之间的任何数字都将在数组中。如果你缓存最小值和最大值,那么第一个搜索是O(N),其他的都是O(1)

    如果数组的元素是分数而不是整数,那么还有一个额外的先决条件测试,fractionPart(min(array))-fractionPart(number)==0

    【讨论】:

      猜你喜欢
      • 2021-05-03
      • 2021-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-15
      • 2015-03-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多