有重复项时对最左/最右元素进行二分搜索
Posted
技术标签:
【中文标题】有重复项时对最左/最右元素进行二分搜索【英文标题】:Binary search a left/right most element when there are duplicates 【发布时间】:2019-11-13 09:55:33 【问题描述】:当数组中有重复项时,如何在 .NET 中使用二进制搜索找到最左边或最右边的元素?是的,有二分搜索,但如果我们有重复,它就不方便了。
Array.BinarySearch()
返回某个元素的索引,该索引等于搜索到的元素,即如果它在那里偶然发现的第一个元素。来自文档:
允许重复元素。如果 Array 包含多个等于 value 的元素,则该方法仅返回其中一个的索引,不一定是第一个。
这是一个例子。我们有一个数组1 1 1 2 2 2 3 3 3
。该方法应始终返回最左边的出现,即对于 2,它返回 3,对于 3 - 6,等等。
它的便捷实现存在于 Python 中,bisect.bisect_left
,我想知道为什么臭名昭著的 .NET 没有这样的实现?
是的,我们可以使用 e 和 e-1 运行两次二进制搜索,但是如果我们有很多相邻的重复项,就像上面的例子一样?那么就可以向左移动,但是如果有很多重复呢?
【问题讨论】:
所以您假设数组已排序,对吧? 当然,数组已经排序了。 您的问题到底是什么?怎么做(那么你应该谈谈你自己的尝试)?或者为什么它不是内置的?还是两者兼而有之? 两者。你将如何在.NET(你的代码)中做到这一点。如果你能回答第二个就太好了 【参考方案1】: // Returns left-most index to insert element, ie. it can return 0 or array.Count()
public int BinarySearchLeft<T>(IList<T> array, T element)
where T : IComparable
int lo = 0;
int hi = array.Count();
while (lo < hi)
int mid = (lo+hi) / 2;
if (array[mid].CompareTo(element) < 0)
lo = mid + 1;
else
hi = mid;
return lo;
【讨论】:
你在重复同样的问题。 @YvesDaoust 为什么?它找到最左边的,因为它丢弃了相等的。 对不起,我的错。【参考方案2】:你可以做一个辅助数组来阻止遍历item,如果它被遍历一次!这个想法很简单——
一旦找到键,将其标记为遍历(这样,如果第二次获得相同的项目,您可以识别相同的索引项目是否遍历,一种 dfs :) 可以使用布尔数组这。并继续这样做,直到你得到一个不是你的“钥匙”的物品;
1.1 如果您找到了该项目,请检查当前索引是否小于或不与先前找到的索引[对于最左边的索引]进行比较,如果是,则将其替换为当前索引。
1.2 如果您找到该项目,请检查当前索引是否大于或不与先前找到的索引进行比较 [for right index] 如果是则将其替换为当前索引。
对其余项目使用常规二进制搜索算法。
遍历整个数组后,返回 Pair of left and right most index.
这里是上述方法的实现:[In Java]
public class LeftRightMostOcc
static class Pair
int left, right;
// initial value for left and right pair
public void setLeftMostPair()
this.left = Integer.MAX_VALUE;
public void setRightMostPair()
this.right = Integer.MIN_VALUE;
private static Pair binarySearchUtil(int [] a, int key, int low, int high, boolean [] visited, Pair obj)
if(high >= low)
int middle = low + ((high-low)/2);
// found key, and not visited before
if(a[middle] == key && !visited[middle])
// update if less value you get, for left pair
if(middle < obj.left)
obj.left = middle;
// update if right value you get, for right pair
if(middle > obj.right)
obj.right = middle;
// mark the index
visited[middle] = true;
// keep on doing this, until you get an item which is not your "key"
Pair flag = binarySearchUtil(a, key, low, middle-1, visited, obj);
if(flag != null)
flag = binarySearchUtil(a, key, middle+1, high, visited, obj);
// apply regular binary search algo for the rest of the item.
else if(a[middle] > key)
return binarySearchUtil(a, key, low, middle-1, visited, obj);
else
return binarySearchUtil(a, key, middle+1, high, visited, obj);
return obj;
private static Pair binarySearch(int [] a, int key, int low, int high)
boolean [] visited = new boolean[a.length];
Pair object = new Pair();
object.setLeftMostPair();
object.setRightMostPair();
return binarySearchUtil(a, key, low, high, visited, object);
public static void main(String[] args)
int [] a = 1,1,1,2,2,2,2,3,3,3,4,5,5,5,5;
Pair object = binarySearch(a, 2, 0, a.length-1);
System.out.println("LeftMost Index: "+object.left+" - RightMost Index: "+object.right);
O/P:LeftMost 索引:3 - RightMost 索引:6 [for key - 2]
O/P:LeftMost 索引:10 - RightMost 索引:10 [for key - 4]
O/P:LeftMost 索引:11 - RightMost 索引:14 [for key - 5]
【讨论】:
所以在一次运行中它会同时获得最左边和最右边。如果是 .NET/C# 就好了。【参考方案3】:您可以通过提供修改后的键比较函数来解决此问题,如果两个比较元素相等并且与前一个元素的比较也返回相等,则返回更大。这样,只有运行的第一个元素会返回相等。
这个有点做作,最好自己实现搜索功能。
【讨论】:
以上是关于有重复项时对最左/最右元素进行二分搜索的主要内容,如果未能解决你的问题,请参考以下文章
Java算法 -- 二分查找:查找目标元素最左的位置和最右的位置局部最小值问题求解
Java算法 -- 二分查找:查找目标元素最左的位置和最右的位置局部最小值问题求解