查找是不是存在任何 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 &gt;= 0 因为 j 在每一步变化为 1 但 array[j] 变化至少为 1(不同的和排序的数字)。

所以左边是&lt;=0,右边是&gt;= 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] &gt; 3,并且您可以正确推断出高于该枢轴点的任何值都不可能成为array[i] == i。这是因为列表是有序且唯一的,因此您不能以这样的组合结束:

0 1 3 4 5 5 6
0 1 3 4 6 5 7

一旦确定array[i] 大于ii 和任何给定的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 的算法的主要内容,如果未能解决你的问题,请参考以下文章

排序 查找

java在数组里查找数据

c语言,查找数组中是不是存在某个数?

php基础查找算法

查找数组中任何一对相等元素之间的最小距离

LeetCode 941. 有效的山脉数组(Valid Mountain Array)