查找是不是存在任何 i 使得 array[i] 等于 i 的算法
Posted
技术标签:
【中文标题】查找是不是存在任何 i 使得 array[i] 等于 i 的算法【英文标题】:Algorithm to find if there is any i so that array[i] equals i查找是否存在任何 i 使得 array[i] 等于 i 的算法 【发布时间】:2011-05-05 07:23:47 【问题描述】:我的 CS 教授给我一个作业:
在 O(logn) 时间内找到,如果在给定的不同整数的预排序数组中存在索引 i,则 array[i] = i。证明时间是O(logn)。
更新:整数可以是负数、0 或正数。
好的,所以我在这方面有点挣扎。我的想法是这样的:
使用二分查找,如果array[mid]
数组右半部分对应的规则是array[mid] >= startindex + numel,其中变量如上,numel为mid右边的元素个数。
这看起来不像 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 之间的值。因此,我继续搜索它们。这是错的吗?
【问题讨论】:
输入数组中的数字是否保证唯一? @Adam 是的,它们是不同的。 【参考方案1】:整数是不同的且已排序。
给定我这样array[i] = i
你有array[i] - i = 0
。
对于每个 j array[j] - j <= 0 并且对于 j > i 你有 array[j] - j >= 0
因为 j 在每一步变化为 1 但 array[j] 变化至少为 1(不同的和排序的数字)。
所以左边是<=0
,右边是>= 0
。
使用二分法,您可以在O(log n)
中轻松找到正确的位置。
请注意,您只需要找到一个元素,而不是所有元素。在您的示例中,所有元素都可以正常工作,但您只需要其中一个。如果你想全部打印出来,那就是
O(n)
..
【讨论】:
嗯...是的,我能理解你在说什么,但不完全是你的意思意思。如果我们检查每个子数组的中间元素,我们只能在某些条件下(我列出)确定左边或右边没有这样的元素。否则,我们必须检查左右两边,然后我们检查了多个登录条目,在最坏的情况下。将用测试用例更新我的问题。你是说我的程序没有应有的效率? 这就像一个“太低/太高”的游戏:从 0 (=) 开始。测试 n/2 :如果 array[i] - i 是递增的(不一定严格) @Maxmalmgren:你永远不必同时检查左右,因为它是一个排序列表;每个子列表也会被排序,所以如果你的中点检查高于索引的值,你只需要检查左边;如果中点检查较低,则只需向右检查即可。 不,只要它们是不同的,一切都会正常进行。在每一点,你要么找到一个明确的例子,其中 a[i]=i,要么你知道左边没有例子,或者你知道右边没有例子。 @Max Malmgren :它不会改变任何东西。您是否同意函数 i --> array[i] - i 正在增加?然后你只需要找到这个函数等于 0 的一个点。如果你是正的,那么任何 0 必须在你的左边,如果你是负的,那么它必须在你的右边。【参考方案2】:将二进制搜索想象为在字典中查找单词。您可以先将书准确地打开到字典的中心,然后查看页面顶部的单词是在您要查找的单词之前、之后还是等于您要查找的单词。如果是在之后,则将字典的后半部分一分为二并检查该半部分的中间部分。查看页面顶部后,您已将要搜索的区域缩小到字典的四分之一以内。你继续这个过程,直到你发现这个词在你正在查看的页面上的某个地方。然后,您使用类似的过程在该页面上查找单词。
这个过程不是 O(n),因为您不必查看每一页上的每个单词,即使在最坏的情况下也是如此。这是 O(log n),因为在每一步中,您都能够消除大约一半的字典,因为 not 包含您要查找的单词。
编辑
对不起,我误解了原来的问题。
在这种情况下,关键是要认识到所谓的“皮江孔原理”,它指出您只能将尽可能多的皮江放入孔中。(留给学术界为这样一个简单的想法想出一个名字!)
考虑以下情况:
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
。
【讨论】:
谢谢!对于不太复杂的详尽解释。【参考方案3】:这是一个二分查找问题没有给出键。在 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
【讨论】:
【参考方案4】:你的直觉是正确的使用二分搜索;你的分析不是。请记住,它是一个排序列表,因此在二进制搜索条件下,您需要搜索 MAXIMUM 个 log(n) 条目。
【讨论】:
【参考方案5】:我会尽量不泄露答案,但我会指出一些观察结果:
检查中间元素时,有3种情况。第一个当然是array[i] == i,在这种情况下算法终止。在其他两种情况下,我们能够丢弃元素本身以及大约一半的输入。
现在,如果 array[i] > i,当我们向右移动时,数组索引 (i) 是否有可能“赶上”数组值?请记住数组值的排序不同的属性。
【讨论】:
【参考方案6】:因为数组 A 已排序。 A[i]>=A[i-1]+1 => A[i]-i >= A[i-1]-(i-1),令 B[i] = A[i]-i, B[] 是一个排序数组,可以使用二进制搜索在 lgn 时间内搜索到 B[x]==0。
【讨论】:
【参考方案7】:Array B[i] = A[i]-i
可能无法排序,即使 A[i] 已排序但有重复项:
考虑这个例子:
我:0 1 2 3 4 答:1 1 2 4 4
B[0] = A[0]-0 = 1, B[1] = A[1] -1 = 0 , ...
B:1 0 0 1 0
【讨论】:
问题陈述说数字是不同的。【参考方案8】: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);
【讨论】:
稍微解释一下你的代码。仅代码答案不受欢迎。【参考方案9】:输入:排序后的 n 个不同整数的数组 A,可以是正数、负数或零。
输出:如果有一个索引i满足A[i] = i,则返回True,否则返回False。
运行时间:O(logN)
分而治之的策略用于通过使用名为midpoint的变量将数组减半来将数组划分为子数组。目标是递归地将数组分成两个分支,直到两个分支中的每一个中都剩下一个元素。
当通过比较低点和中点索引找到单个元素时,对左分支和右分支进行 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
"""
【讨论】:
以上是关于查找是不是存在任何 i 使得 array[i] 等于 i 的算法的主要内容,如果未能解决你的问题,请参考以下文章