面试问题 - 在排序数组 X 中搜索索引 i 使得 X[i] = i

Posted

技术标签:

【中文标题】面试问题 - 在排序数组 X 中搜索索引 i 使得 X[i] = i【英文标题】:Interview question - Search in sorted array X for index i such that X[i] = i 【发布时间】:2011-05-09 12:36:06 【问题描述】:

昨天我在面试中被问到以下问题:

考虑一个 Java 或 C++ 数组,比如 X,它已排序并且其中没有两个元素是相同的。如何最好地找到一个索引,比如i,这样该索引处的元素也是i。那是X[i] = i

作为澄清,她还给了我一个例子:

Array X : -3 -1 0 3 5 7
index   :  0  1 2 3 4 5

Answer is 3 as X[3] = 3.

我能想到的最好的方法是线性搜索。面试后我虽然对这个问题说了很多,但找不到更好的解决方案。我的论点是:具有所需属性的元素可以在数组中的任何位置。所以它也可能在数组的最后,所以我们需要检查每个元素。

我只是想从这里的社区确认我是对的。请告诉我我是对的:)

【问题讨论】:

一些类似于二分搜索的算法应该会给出更好的解决方案 我认为亚马逊不希望你公开他们的面试问题...... 4 个收藏夹?! 5个赞?!您(支持者、最喜欢的点击者)来自哪里?这是一个非常简单的问题。我不会找一个对二进制和插值搜索一无所知的求职者。 Peter:我已经编辑删除了公司名称。 Alexey:我知道二分搜索,但想不出如何应用它。 @Alexey:如果他们只是想知道应聘者是否知道二分搜索,那么他们只会要求找到i,其中x[i] = 3在排序后大批。这个问题很有趣。如果应聘者立即回答,他们很可能以前见过,但他们可能很聪明,并立即发现了额外的技巧。如果候选人在 30 秒后回答,他们就发现了诀窍。如果他们不能回答,他们就没有发现它。要将聪明人和知识渊博的人区分开来,请用float 的数组询问它,看看您是否得到相同(现在不正确)的答案;-) 【参考方案1】:

这可以在O(logN) 时间和O(1) 空间中通过使用稍作修改的binary search 来完成。

考虑一个新数组Y,这样Y[i] = X[i] - i

Array X : -3 -1   0  3  5  7
index   :  0  1   2  3  4  5
Array Y : -3 -2  -2  0  1  2

由于X 中的元素是递增 顺序的,所以 新数组 Y 将按 非递减 顺序排列。所以一个二进制 在Y 中搜索 0 将给出答案。

但是创建Y 将占用O(N) 空间和O(N) 时间。所以而不是 创建新数组,您只需修改二进制搜索,以便 对Y[i] 的引用被X[i] - i 替换。

算法:

function (array X) 
       low  = 0
       high = (num of elements in X) - 1

       while(low <= high) 
               mid = (low + high) / 2

               // change X[mid] to X[mid] - mid
               if(X[mid] - mid == 0)
                       return mid

               // change here too
               else if(X[mid] - mid < 0)
                       low = mid + 1;

               else
                       high = mid - 1;
       end while

       return -1 // no such index exists...return an invalid index.

end function

Java implementation

C++ implementation

【讨论】:

感谢您的解释。我知道二进制搜索,但我从来没有以这种方式应用它。 @codaddict:“非降序”是升序吗? 我不明白为什么结果数组 Y 不递减。假设 X 是 [3,3,3,3],那么 Y 就是 [3, 2, 1, 0]。明显在减少。我想我在这里遗漏了一些东西。有人可以请赐教吗? @Srikanth:这个问题有一个要求“没有两个数组元素是相同的”。 一切都很好,除了一件事:你并没有真正证明数组 Y 是非递减的(这对于给定的条件是正确的),因此你没有突出显示使其非递减的条件- 减少。如果使用浮点数而不是整数会发生什么变化?在您的解释中,没有任何变化,但算法对于浮点数是错误的,真正的算法是 o(n)。【参考方案2】:

有一些更快的解决方案,平均 O(log n) 或在某些情况下 O(log log n) 而不是 O(n)。 “二分搜索”“插值搜索” google,你可能会找到很好的解释。

如果数组是未排序的,那么是的,元素在任何地方,你不能低于 O(n),但排序数组不是这种情况。

--

根据要求对插值搜索进行一些解释:

虽然二分搜索只涉及比较两个元素的“更大/不更大”,但插值搜索也尝试使用数值。关键是:您有一个从 0 到 20000 的排序范围。您寻找 300 - 二分搜索将从范围的一半开始,即 10000。插值搜索猜测 300 可能更接近 0大于 20000,因此它将首先检查元素 6000 而不是 10000。然后再次 - 如果它太高,则递归到较低的子范围,并且它太低 - 递归到上部子范围。

对于具有 +- 均匀分布值的大数组,插值搜索应该比二分搜索快得多 - 编写代码并自己查看。 此外,如果首先使用一个插值搜索步骤,然后使用一个二分搜索步骤,以此类推,效果最好。

请注意,这是人类在字典中查找内容时的直觉行为。

【讨论】:

+1 为interpolation search 参考。在 D. Knuth 的书中也有描述。 你能解释一下如何应用插值搜索来解决这个问题吗? -1,您还没有解释如何将插值搜索应用于此问题。不明显的步骤是对于整数,如果x[i] 严格增加,则x[i]-i 不减少。 @Kos:问题不是“我如何找到索引i 以便x[i] == 300”。问题是,“我如何找到索引i 以便x[i] == i”。如果不考虑这一点,我看不出这个答案如何被认为是正确的(尽管它肯定是对插值搜索的一个很好的描述)。 ...史蒂夫你是对的:),由于某种原因,直到现在我还没有注意到这个问题是关于什么的; aparrently 我需要放弃我的编程技能,而是专注于阅读技能。约翰,对于插值搜索,只需参考 Codaddict 的帖子并将 int mid = ... 行替换为适合插值搜索的行。抱歉给大家带来了困惑:)。【参考方案3】:

它不需要按照@codaddict 在answer 中的建议考虑任何数组Y

使用二分查找并检查给定数组的中间元素,如果它低于它的索引,我们不需要检查任何更低的索引,因为数组是 已排序,因此如果我们向左移动,减去 m 个索引和(至少)m 个值,所有后续元素也将太小。例如。如果arr[5] = 4 然后arr[4] &lt;= (4 - 1)arr[3] &lt;= (4 - 2) 等等。如果中间元素大于其索引,则可以应用类似的逻辑。

这里是简单的Java 实现:

int function(int[] arr) 
        int low = 0;
        int high = arr.length - 1;

        while(low <= high) 
            int mid = high - (high - low) / 2;

            if(arr[mid] == mid) 
                 return mid;
             else if(arr[mid] < mid) 
                 low = mid + 1;
             else 
                 high = mid - 1;
            
        

        return -1; // There is no such index

请注意,仅当所有元素都不同时,上述解决方案才有效。

【讨论】:

如果arr[5] = 3,那么解决方案仍然可以在arr[4] 中或在arr[6] 中。在这种情况下,我认为我们不能用上面的代码选择一个方向 @user814628,在这种情况下arr[4]必须小于3,因为arr是排序的,所以我们必须从索引6开始寻找解决方案。【参考方案4】:

我认为这会更快。

从列表中间开始

如果 X[i] > i 则走到剩下的左边的中间

如果 X[i]

继续这样做,它会将每个循环的可能元素数量减少一半

【讨论】:

【参考方案5】:

您可以执行二分搜索: 搜索中间,如果该值低于索引,则没有更低的索引将包含相同的值。

然后你搜索上半部分,并继续直到找到元素,或者达到一个元素跨度。

【讨论】:

“你可以执行二分搜索” - 你的密钥是什么? 单元格的值为“值”,索引为“键”。 (假设知道数组的长度) -1,单元格的值不是用于二分查找的值。 +1:我认为您的解释不如其他一些人(例如 JOTN)那么清楚,但似乎您是第一个给出答案的人。【参考方案6】:

这是我想出的一个解决方案,如果有重复,它就可以工作(我错误地忽略了没有重复的警告)。

//invariant: startIndex <= i <= endIndex

int modifiedBsearch(int startIndex, int endIndex)

   int sameValueIndex = -1;
   int middleIndex = (startIndex + endIndex) /2;
   int middleValue = array[middleIndex];
   int endValue = array[endIndex];
   int startValue = array[startIndex];

   if(middleIndex == middleValue)
      return middleValue;
   else 
      if(middleValue <= endIndex)
         sameValueIndex = modifiedBsearch(middleIndex + 1, endIndex)

      if(sameValueIndex == -1 && startValue <= middleIndex)
         sameValueIndex = modifiedBsearch(startIndex, middleIndex -1);
   

   return sameValueIndex;


我猜这需要 O(log n) 时间,但乍一看并不清楚???

如果运气不好,会花费 O(n log n) 时间(堆栈树的高度为 log n,它会是一棵完整的树,最后一层有 n 个节点,n/ 2 在倒数第二,等等)。

因此,平均而言,它将介于 O(log n) 和 O(n log n) 之间。

【讨论】:

不可能是 O(n log n),每个元素最多访问一次。【参考方案7】:

在我的脑海中,进行二进制拆分可能会更快。

看中间值,如果高那么你需要的,在下半部分重新搜索。

经过一次比较,您已经将数据集分成了一半

【讨论】:

【参考方案8】:

阅读问题后,似乎有一种情况可用于加快查找速度。在将位置与值进行比较时,如果值大于该位置,则该值可以用作下一个要评估的位置。这将有助于更快地跳过数组。可以这样做,因为数组已排序。我们正在跳过的值在概念上被移到数组的左侧并且位于错误的位置。

例子:

int ABC[] =  -2, -5, 4, 7, 11, 22, 55 ;

如果我当前的位置是 2,它的值是 4,它们不相等,从概念上讲,值 4 向左移动。我可以将 4 的值用作我的下一个位置,因为如果值 4 不在位置上,那么小于 4 的所有东西也都不在位置上。

一些示例代码仅供讨论:

void main()

    int X[] =  -3, -1, 0, 3, 5, 7;
    int length = sizeof(X)/sizeof(X[0]);

    for (int i = 0; i < length;) 
        if (X[i] > i && X[i] < length)
            i = X[i];                 // Jump forward!
        else if (X[i] == i) 
            printf("found it %i", i);
            break;
         else
            ++i;
    

【讨论】:

【参考方案9】:

我猜修改版的二分搜索就足够了

假设序列是

Array : -1 1 4 5 6
Index :  0 1 2 3 4

Result : 1

Array : -2 0 1 2 4 6 10
Index :  0 1 2 3 4 5 6 

Result: 4

从这两个示例中我们看到,如果 mid

mid <- (first + last )/2

if a[mid] == mid then
       return mid

else if a[mid] < mid then
        recursive call (a,mid+1,last)

else 
         recursive call (a,first,mid-1)

【讨论】:

【参考方案10】:

Java:

public static boolean check (int [] array, int i)

    if (i < 0 || i >= array.length)
        return false;

    return (array[i] == i);

C++:

bool check (int array[], int array_size, int i)

    if (i < 0 || i >= array_size)
        return false;

    return (array[i] == i);

【讨论】:

以上是关于面试问题 - 在排序数组 X 中搜索索引 i 使得 X[i] = i的主要内容,如果未能解决你的问题,请参考以下文章

在 mongoDB 中搜索索引数据的复杂度(Big-O)是多少?

在数组中搜索特定范围内的整数

数据库中的索引 [重复]

给定一个数组 A 和 m 个查询

如何从飞镖颤动中的键或键值对获取索引

inode索引节点和硬连接软连接(草稿)