【问题标题】:How to determine the boundaries in binary search?如何确定二分查找的边界?
【发布时间】:2018-12-17 14:53:05
【问题描述】:

我知道二分搜索是如何工作的,但是当我需要实现一个时,我总是会犯一些小错误。

以下代码是leetcode 287 find the duplicate number的解决方案

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int low = 1, high = nums.size() - 1;
        while (low < high) {
            int mid = low + (high - low) * 0.5;
            int cnt = 0;
            for (auto a : nums) {
                if (a <= mid) ++cnt;
            }
            if (cnt <= mid) low = mid + 1;
            else high = mid;
        }
        return low;
    }
};

有几个地方我很困惑:

1.while循环的条件low&lt;high or low&lt;=high

2.a&lt;=mid or a&lt;mid(本例专用)

3.cnt&lt;= mid or cnt&lt;mid

4.low=mid+1 or low=mid

5.high=mid or high=mid-1

6.我返回哪个值?

有没有一种好方法可以记住或理解要使用的正确组合?

【问题讨论】:

    标签: binary-search


    【解决方案1】:

    在编写二分搜索时,需要考虑几件事情。首先是您要搜索的区间范围,特别是您如何定义它。

    例如,它可以包含lowhigh,即[low, high],但也可以不包含high[low, high)。你选择哪一个会改变你算法的其余部分。

    明显的含义是初始值。一般来说,high 应该是数组的长度(如果它是独占的,它应该是包含的长度),但它可能是完全不同的东西,具体取决于您要解决的问题。

    对于 while 循环,您希望它在搜索间隔为空时终止,这意味着没有更多的候选者可以检查。如果您使用区间[low, high],那么当low 严格大于high(例如,[5,5] 包含 5,但 [6,5] 不包含任何内容)时,这将为空,因此 while 循环将检查相反,low &lt;= high。但是,如果使用区间[low, high),那么当low等于high时这个区间是空的,所以while循环需要检查low &lt; high

    在 while 循环中,检查 mid 后,您希望将其从间隔中删除,以便不再检查它。如果high 包含在内,那么您必须使用比mid 少一个作为新的high 才能将其从区间中排除。但如果high 是独占的,那么将high 设置为等于mid 就足以排除它。

    至于何时更新 lowhigh,这取决于您要搜索的内容。除了基本的二分搜索,您只想知道某物是否确实存在于集合中,您还必须考虑在尽可能接近时该怎么做。

    例如,在 C++ 中,binary_search 更有用的版本称为 lower_boundupper_bound。如果要搜索的值在容器中不存在,则它们都返回相同的位置,即大于搜索值的第一个位置。这很方便,因为如果要保持容器排序,这是您应该插入该值的位置。但是,如果值在容器中,可能多次出现,那么 lower_bound 将返回该值的第一次出现,而 upper_bound 仍将返回大于该值的第一个位置(或者换句话说,右边界到值的位置)。

    要获得这些不同的行为,当mid 等于搜索值时,您可以更新lowhigh 绑定。如果你想要下限,那么你想继续搜索你的搜索范围的下半部分,所以你把high向下。如果您想要上限,则将low 提高。在您的示例中,当cnt == mid 时,它会提升low,因此它会找到一个上限。

    至于返回什么,这取决于您的搜索间隔和您要查找的内容。在您的示例中,while 循环正在检查(low &lt; high),因此lowhigh 在中断时将是相等的,并且您使用哪个并不重要,但即便如此您可能想要返回left - 1 或@ 987654362@ 取决于问题。如果 while 循环是 (low &lt;= high),那么它什么时候会中断 low == high + 1,所以这取决于您要查找的内容。当你有疑问时,你总是可以通过一个例子来思考。

    所以为了使用这一切,这里是你提到的解决方案的一个版本,但使用 [low, high] 而不是 [low, high) 的间隔:

    class Solution {
        public:
            int findDuplicate(vector<int>& nums) {
                int low = 1, high = nums.size() - 2;
                while (low <= high) {
                    int mid = low + (high - low) * 0.5;
                    int cnt = 0;
                    for (auto a : nums) {
                        if (a <= mid) ++cnt;
                    }
                    if (cnt <= mid) low = mid + 1;
                    else high = mid - 1;
                }
                return low;
            }
        };
    

    PS:我没有提到间隔 (low, high](low, high) 的原因是因为它与计算 mid 索引的数学运算混淆了。因为 int math 会向下取整,所以您最终可能会遇到再次搜索 mid 的情况。比如low是7,high是9,那么low + (high - low) * 0.5就是8。将low更新到8后(因为是排他的就不加1了),low + (high - low) * 0.5还是8,你的循环永远不会终止。您可以通过在除以 2 的部分上加 1 来解决此问题,但通常使用包含 low 的区间会更干净。

    【讨论】:

      【解决方案2】:

      您可以使用 leetcode 的二进制搜索指南作为参考。 https://leetcode.com/explore/learn/card/binary-search

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多