在特定元素上查找排序数组的范围索引
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=0
和max=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 > 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
【讨论】:
以上是关于在特定元素上查找排序数组的范围索引的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin - 在索引范围内的 IntArray 中查找最小值