魔术阵列索引时间/空间复杂度

时间:2015-12-04 20:27:09

标签: arrays algorithm

我一直在关注以下问题:

  

魔术索引:数组A[0...n-1]中的魔术索引被定义为索引i,例如A[i] = i。给定一个排序的非不同整数数组,编写一个方法来查找一个魔术索引(如果存在)。

这是我的解决方案:

static int magicNonDistinct(int[] array, int start, int end) {
  if (end < start) return -1;
  int mid = start + (end - start) / 2;
  if (mid < 0 || mid >= array.length) return -1;

  int v = array[mid];
  if (v == mid) return mid;

  int leftEnd = Math.min(v, mid - 1);
  int leftRes = magicNonDistinct(array, start, leftEnd);
  if (leftRes != -1) return leftRes;

  int rightStart = Math.max(v, mid + 1);
  int rightRes = magicNonDistinct(array, rightStart, end);
  return rightRes;
}

它工作得很好,是Cracking The Code Interview第6版,问题8.3跟进(对不起破坏)一书的推荐解决方案。

然而,当在没有魔术索引的不同数组上运行它时,它会访问所有元素,从而产生最坏的O(n)运行时间。

由于它是递归的,因此最坏情况下需要O(n)内存。

为什么这个解决方案比迭代数组更可取?我认为这个解决方案(我自己的)更好:

static int magicNonDistinctV2(int[] array) {
  for (int i = 0; i < array.length; ++i) {
    int v = array[i];
    if (v == i) return v;
    if (v >= array.length) return -1;
    else if (v > i) i = v - 1;
  }

  return -1;
}

O(n)运行时间O(1)空间总是?

有人可以为初始算法获得更好的时间复杂度吗?我一直在考虑是否是O(d),其中d是不同元素的数量,但是这种情况也是错误的,因为最小值/最大值仅在一个方向上起作用(想想是否{{1}并且数组的下半部分都是五个。)

编辑:

好的人认为我看到香蕉并且只要看到二进制搜索的东西就会尖叫O(log(n))。很抱歉不清楚大家。

让我们谈谈我发布的第一篇文章中的代码(CTCI的解决方案):

如果我们有一个如下所示的数组:v = 5, mid = 4,实际上是一个如下所示的数组:[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8],其大小为[-1,...,n-2],我们知道没有可匹配的元素。但是 - 算法将访问所有元素,因为元素不是唯一的。我敢说,运行它,它不能像在常规二进制搜索中那样将搜索空间除以2。请告诉我我的推理有什么问题。

3 个答案:

答案 0 :(得分:3)

不,在我看来第一个解决方案不是O(log n),因为其他答案状态,它确实是O(n)最坏的情况(在最坏的情况下它仍然需要经过所有元素,考虑等价数组如作者所述,转移了一个。)

它不是O(log n)的原因是因为它需要在中间的两边搜索(二进制搜索只检查中间的一边因此它是O(log n))。

如果你很幸运,它允许跳过项目,但是你的第二个迭代解决方案也会跳过项目,如果不需要查看它们(因为你知道在数组排序的范围内不能有魔法索引)所以在我的意见第二种解决方案更好(相同的复杂性+迭代,即更好的空间复杂性,没有相对昂贵的递归调用)。

编辑:但是当我再次考虑第一个解决方案时,另一方面允许在可能的情况下“向后跳”,迭代解决方案不允许 - 例如考虑像{-10,-9这样的数组,-8,-7,-6,-5} - 迭代解决方案需要检查所有元素,因为它从头开始并且值不允许向前跳过,而当从中间开始时,算法可以完全跳过检查上半场,然后是下半场的上半场等等。

答案 1 :(得分:2)

  • 你是对的,最坏的情况是O(n)。您可能必须访问阵列的所有元素。
  • 只有一个理由访问数组元素[mid,end],那就是array[mid] > end时(因为在这种情况下,神奇指数肯定不会出现在[mid]中,结束]元素)。
  • 同样,访问数组元素[start,mid]只有一个原因,那就是array[start] > mid
  • 所以,希望你可能不必访问所有元素。因此,这是一种可能有效的优化。

因此,这种类二进制方法似乎比线性迭代整个数组要好,但在最坏的情况下,你会命中O(n)。

PS:我假设数组按升序排序。

答案 2 :(得分:0)

您似乎误解了所需解决方案的时间复杂性。最糟糕的情况不是O(n),而是O(log(n))。这是因为在每次传递期间,您下次只搜索数组的一半。

这是一个C++ example并检查对于11个元素的整个数组,它只需要3次检查。