在特定元素上查找排序数组的范围索引

Posted

技术标签:

【中文标题】在特定元素上查找排序数组的范围索引【英文标题】:Finding range index of a sorted array on a specific element 【发布时间】:2016-09-26 21:42:38 【问题描述】:

我正在使用对集合进行排序的数组。任务是找到一个值的范围。

假设我们有这个排序数组:

int[] array = 1,1,1,3,3,9,10

示例 我们必须找到元素 1 的范围最小值和最大值。我们很快发现元素 1 的第一个索引是 0,范围最大值是 2。

我们现在知道 0 到 2 之间的范围都有值 1。如果搜索的元素是 3,我们看到范围是 3-4,而元素 9 的范围是 6-6。

我已经使用线性搜索来执行此操作,但如果有更快的方法来执行此操作,我会听到吗?

【问题讨论】:

【参考方案1】:

您可以使用两种版本的二分搜索来查找最左边和最右边的位置:

int[] array =  1, 1, 1, 3, 3, 9, 10 
int value = ...

int leftmost(int min, int max)

    if (min == max) return min
    int mid = (min + max) / 2

    if (array[mid] < value) return leftmost(mid + 1, max)
    else return leftmost(min, mid)


int rightmost(int min, int max)

    if (min == max) return min
    int mid = (min + max + 1) / 2

    if (array[mid] > value) return rightmost(min, mid - 1)
    else return rightmost(mid, max)

只需使用min=0max=array.length-1 拨打电话即可。时间复杂度为O(logn)

你会得到这样的输出(0-based 索引):

value=1  -> left=0, right=2
value=3  -> left=3, right=4
value=9  -> left=5, right=5
value=10 -> left=6, right=6

【讨论】:

时间复杂度将是 logn 而不是 nlog(n) 我不建议完全使用这个公式,因为如果搜索值不在数组中,您仍然会返回一些有效的索引。最好使用传统的if (min &gt; max) return -1 提前退出。然后你必须想出一个新的检查来确定成功......【参考方案2】:

二分查找会快得多。您可以对此here

有所了解

【讨论】:

二分查找的问题是它只返回一个索引。例如。如果我们搜索元素 1,它将返回 1 作为索引。但我正在寻找一个范围。【参考方案3】:

假设一个更大的数组先是二进制然后是线性的。

根据数组的大小,更快的方法可能是线性的。 如果您使用 7 个数字的数组重复进行此搜索,那么您不会看到太大的性能差异。实际上它可能会更慢。

但是,尽管扩展该数组以便拥有更多数据,并且最好先进行二分搜索,然后再进行线性搜索。

执行一次并抓取范围的底部,然后进行线性以找到范围的终点。

你可以为终点做另一个二进制,但随着时间的推移,它应该平均到一个简单的线性函数的洗涤。

【讨论】:

【参考方案4】:

虽然@Arturo Menchaca 的答案是正确的,但我认为递归解决方案可能有点难以理解。因此,对于那些喜欢迭代方法的人...

public int[] FindRange(int[] arr, int value)

    int[] upperLowerBounds = new int[2];
    int startIndex = 0;
    int endIndex = arr.Length - 1;
    while (startIndex < endIndex)
    
        int midIndex = (startIndex + endIndex) / 2;
        if (value <= arr[midIndex])
        
            endIndex = midIndex;
        
        else
        
            startIndex = midIndex + 1;
        
    

    // saving startIndex in the result array that will be returned...
    upperLowerBounds[0] = startIndex;
    endIndex = arr.Length - 1;
    while (startIndex < endIndex)
    
        int midIndex = (startIndex + endIndex) / 2 + 1;
        if (value < arr[midIndex])
        
            endIndex = midIndex  -1;
        
        else
        
            startIndex = midIndex;
        
    

    upperLowerBounds[1] = endIndex;
    return upperLowerBounds;

和单元测试..

[TestCase(new int[]  -4, -3, -3, -3, -3, -3, -3, -3, -3, -3, -1, 1, 6, 8, 10, 10, 10, 10, 10, 10, 25 , -3, 1, 9)] // search element = -3
[TestCase(new int[]  -4, -3, -1, 1, 6, 8, 10, 10, 10, 10, 10, 10, 25 , 6, 4, 4)] // search element = 6
[TestCase(new int[]  -4, -3, -1, 1, 6, 8, 10, 10, 10, 10, 10, 10, 25 , 10, 6, 11)] // search element = 10
[TestCase(new int[]  -4, -3, -3, -3, -3, -3, -3, -3, -3, -3, -1, -1, -1, -1, -1, -1, -1, -1, 1, 6, 8, 10, 10, 10, 10, 10, 10, 25 , -1, 10, 17)] // search element = -1
[TestCase(new int[]  1, 1, 1, 3, 3, 9, 10 , 3, 3, 4)] // search element  = 3
[TestCase(new int[]  1, 1, 1, 3, 3, 9, 10 , 1, 0, 2)] // search element = 1
public void FindRangeTest(int[] arr, int value, int expectedStartIndex, int expectedEndIndex)

    int[] range = runner.FindRange(arr, value);

    // Assert.
    Assert.AreEqual(expectedStartIndex, range[0]);
    Assert.AreEqual(expectedEndIndex, range[1]);

我用过 C# 和 nunit 3.10

【讨论】:

以上是关于在特定元素上查找排序数组的范围索引的主要内容,如果未能解决你的问题,请参考以下文章

返回数组Excel VBA中元素的索引

Task 04:数组二分查找

二分查找最优实现

Kotlin - 在索引范围内的 IntArray 中查找最小值

SwiftUI - 致命错误:从数组中删除元素时索引超出范围

如何根据特定索引范围对 ArrayList<String> 进行排序