有重复项时对最左/最右元素进行二分搜索

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算法 -- 二分查找:查找目标元素最左的位置和最右的位置局部最小值问题求解

Java算法 -- 二分查找:查找目标元素最左的位置和最右的位置局部最小值问题求解

AK leetcode 流浪计划 - 二分查找

二分查找

关于二分搜索 简单左侧区间右侧区间