我的CS教授已经完成了一项任务:
在O(logn)时间内,如果在给定的预先排序的不同整数数组中有一个索引i,那么array [i] = i。证明时间是O(logn)。
更新:整数可以是负数,0或正数。
好吧,所以我对此有点挣扎。我的想法是:
使用二分搜索,我们只能确定中间元素左边没有这样的值,如果array [mid]< = startindex,其中mid是中间元素的索引,startindex是中间元素的开头阵列。
数组右半部分的对应规则是数组[mid]> = startindex + numel,其中变量如上,numel是中间右边的元素数。
这似乎不是O(logn),因为在最坏的情况下我必须遍历整个事情,对吧?有人可以在这里向我说明正确的方向,或者告诉我这有用吗?
我有什么想法可以正式证明这一点吗?我不是要求一个明确的答案,更多的帮助让我理解。
在C:
int _solve_prob_int(int depth, int start, int count, int input[])
{
if(count == 0)
return 0;
int mid = start + ((count - 1) / 2);
if(input[mid] == mid)
return 1;
if(input[mid] <= start && input[mid] >= start + count)
return 0;
int n_sub_elleft = (int)(count - 1) / 2;
int n_sub_elright = (int)(count) / 2;
if(input[mid] <= start)
return _solve_prob_int(depth + 1, mid + 1, n_sub_elright, input);
if(input[mid] >= start + count)
return _solve_prob_int(depth + 1, mid - n_sub_elleft, n_sub_elleft, input);
return _solve_prob_int(depth + 1, mid - n_sub_elleft, n_sub_elleft, input) ||
_solve_prob_int(depth + 1, mid + 1, n_sub_elright, input);
}
测试用例:
Sorted args: 1 2 3 4 5 6 7 8 9 10 11 12 :
Start: 0, count: 12, mid: 5 value: 6
Start: 0, count: 5, mid: 2 value: 3
Start: 0, count: 2, mid: 0 value: 1
Start: 1, count: 1, mid: 1 value: 2
Start: 3, count: 2, mid: 3 value: 4
Start: 4, count: 1, mid: 4 value: 5
Start: 6, count: 6, mid: 8 value: 9
Start: 6, count: 2, mid: 6 value: 7
Start: 7, count: 1, mid: 7 value: 8
Start: 9, count: 3, mid: 10 value: 11
Start: 9, count: 1, mid: 9 value: 10
Start: 11, count: 1, mid: 11 value: 12
以上是我的程序根据搜索方式运行的一些输出。使用1到12的列表,它围绕索引5进行转动,确定在索引0-4处可能存在0-4之间的值。它还确定在索引6-11处可能存在6-11之间的值。因此,我继续搜索它们。这是错的吗?
答案 0 :(得分:13)
整数是有区别和排序的。
鉴于我array[i] = i
你array[i] - i = 0
。
对于每个j&lt;我有array[j] - j <= 0
和j&gt;我有array[j] - j >= 0
因为j在每一步中变化1但是数组[j]至少有1个(不同的和排序的数字)。
所以左边是<=0
右边的>= 0
。
使用二分法,您可以在O(log n)
中轻松找到正确的位置。
<小时/> 请注意,您只需要找到一个元素,而不是所有元素。在您的示例中,所有元素都在工作,但您只需要其中一个元素。如果您要打印它们,它将是
O(n)
..
答案 1 :(得分:8)
想想二元搜索就像在字典中查找单词一样。您可以通过将书完全打开到词典的中心开始,然后查看页面顶部的单词是在您要查找的单词之前,之后还是等于。如果它在之后,你将字典的后半部分分成两部分并检查那部分的中间部分。查看页面顶部后,您在字典的四分之一范围内缩小了搜索区域的范围。您继续此过程,直到您发现该单词位于您正在查看的页面上的某个位置。然后,您使用类似的过程在该页面上查找单词。
此过程不是O(n),因为您不必查看每个页面上的每个单词,即使在最糟糕的情况下也是如此。这是O(log n),因为每一步都可以消除大约一半的字典,因为不包含您要查找的单词。
修改强>
抱歉,我误解了原来的问题。
在这种情况下,关键是要认识到所谓的“皮江孔原理”,它表明你只能在孔中装入尽可能多的皮塞,因为你有适合它们的洞。(留待学术界使用)为这样一个简单的想法提出一个名字!)
考虑以下情况:
0 1 2 3 4 5 6
此处所有 array[i]
等于i
,因此当您第一次开始二元搜索时,您会立即得到肯定答案。
现在让我们从底部拿走一个数字:
0 1 3 4 5 6
当您进行二分查找时,您会发现array[3] > 3
,并且您可以正确地推断出该枢轴点之上的任何值都不可能array[i] == i
。这是因为列表是有序且唯一的,因此您不能最终得到如下组合:
0 1 3 4 5 5 6
0 1 3 4 6 5 7
确定array[i]
大于i
后,i
与任何给定n
之间的数字不足以允许数组中的下一个元素更接近i
。同样,如果您确定array[i]
小于i
,那么当您查看数组的开头时,您没有足够的“空白”可以“赶上”i
因此,在每一步中,您都可以正确地消除一半数组,就像在字典中查找一样,确定O(log n)时间内是否有array[i] == i
。
答案 2 :(得分:2)
这是一个二进制搜索问题,未给出密钥。在OP的问题中,关键在于中间!就是这样,在每个子阵列中搜索mid元素。
使用二进制搜索的解决方案的伪代码:
while (low and high don't meet)
mid = (low + high) / 2
if (arr[mid] < mid)
high = mid - 1
else if (arr[mid] > mid)
low = mid + 1
else // we found it!
return mid;
// end while
return -1; // indicates there is no such i
答案 3 :(得分:1)
你的直觉是正确的二元搜索;你的分析不是。记住它是一个排序列表,所以在二进制搜索条件中,你需要搜索最多的log(n)条目。
答案 4 :(得分:0)
我会尽量不放弃答案,但我会指出一些意见:
检查中间元素时,有3个案例。第一个当然是array [i] == i,在这种情况下算法终止。在另外两种情况下,我们可以丢弃元素本身以及大约一半的输入。
现在,如果array [i]&gt; i,当我们向右移动时,数组索引(i)是否有可能“赶上”数组值?请记住数组值的已排序的不同属性。
答案 5 :(得分:0)
因为数组A已排序。 A [i]> = A [i-1] +1 =&gt; A [i] -i> = A [i-1] - (i-1),设B [i] = A [i] -i,B []是一个可以搜索B [x]的排序数组]使用二分搜索在lgn时间内== 0。
答案 6 :(得分:0)
Array B[i] = A[i]-i
也可能无法排序:
考虑这个例子:
i:0 1 2 3 4
答:1 1 2 4 4B [0] = A [0] -0 = 1,B [1] = A [1] -1 = 0,...
B:1 0 0 1 0
答案 7 :(得分:0)
public static int fixedPoint(int[] array, int start, int end) {
if (end < start || start < 0 || end >= array.length) {
return -1;
}
int midIndex = start +(end-start)/ 2;
int midValue = array[midIndex];
if (midValue == midIndex) {//trivial case
return midIndex;
}
// if there are duplicates then we can't conclude which side (left or right) will
// have the magic index. so we need to search on both sides
//but, we can definitely exclude few searches.
// take the example of the array :
// 0 1 2 3 4 5 6 7 8 9 10 -> indices
// -10, -5, 2, 2, 2, 3(midVal, midIndex = 5), 4, 7, 9, 12, 13 -> array element
// here, midValue < midIndex, but we can't say for sure which side to exclude
// as you can see there are two such magic indices. A[2] = 2 and A[7] = 7. so
// we need to search both sides
// since midValue < midIndex, then we can be sure that between index can't be
// between index number midValue and midIndex-1
/* Search left */
int leftIndex = Math.min(midIndex - 1, midValue);
int left = fixedPoint(array, start, leftIndex);
if (left >= 0) {
return left;
}
/* Search right */
int rightIndex = Math.max(midIndex + 1, midValue);
int right = fixedPoint(array, rightIndex, end);
return right;
}
public static int fixedPoint(int[] array) {
return fixedPoint(array, 0, array.length - 1);
}
答案 8 :(得分:0)
输入:由 n 个不同整数排序的数组 A,这些整数可以是正数、负数或零。
输出:如果存在索引 i 使得 A[i] = i,则返回 True,否则返回 False。
运行时间:O(logN)
分治策略用于通过使用名为中点的变量将数组减半来将数组划分为子数组。目标是递归地将数组分成两个分支,直到两个分支中的每一个都剩下一个元素。
当通过比较低点和中点索引找到单个元素时,对左右分支进行 A[i] = i 的评估。只要两个分支之一为True,程序就结束。
在递归情况下:
return False or True -> True
return False or False -> False
return True or ----- -> True // Short-circuit operator: regardless of the right branch, the result will be True.
这段 Python 代码可以很容易地用 C 或任何其他语言编写:
def isIndex(A, low, high):
midpoint = int( (low + high)/2 )
if low == midpoint:
# base case
print("Left-> low: %d, A[low]: %d. Right-> high: %d, A[high]: %d" % (low, A[low], high, A[high]))
return low == A[low] or high == A[high]
else:
# recursive case
return isIndex(A, low, midpoint-1) or isIndex(A, midpoint, len(A)-1)
A = [0, 1, 3, 5]
print(isIndex(A, 0, len(A)))
"""
Left-> low: 0, A[low]: 0. Right-> high: 1, A[high]: 1
True
"""
A = [-1, 0, 1, 3, 9, 10]
print(isIndex(A, 0, len(A)))
"""
Left-> low: 0, A[low]: -1. Right-> high: 0, A[high]: -1
Left-> low: 1, A[low]: 0. Right-> high: 2, A[high]: 1
Left-> low: 3, A[low]: 3. Right-> high: 3, A[high]: 3
True
"""