面试问题 - 在排序数组 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] <= (4 - 1)
和arr[3] <= (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的主要内容,如果未能解决你的问题,请参考以下文章