二分查找的要点,区间能缩小为一个点

二分查找的要点就是让目标区间不断缩小直至为一个点。

这同样是一些分治算法的目标,比如快速排序,我们的目标是区间缩小为一个点,如果你不能理解这个问题,那么通常会在剩余最后两三个数的时候混乱。

避免死循环,mid的取整问题,向上or向下?

我们在二分查找的时候,要不断通过left right mid的更新去达到我们最终目标;

如果我们的mid计算方式为mid = left + (right - left) / 2,也就是mid向下取整。

那么为了能使目标区间最终能缩小为一个点,我们在更新left的时候,至少要让left前进一步,也就是left = mid + 1。此时mid变换区间为(]。

为什么?如果更新算法是left = mid,那么当left + 1 = right;时,会进入死循环,因为相邻两个数字相加除以2得到的mid=left,这时如果更新left=mid,就会进入死循环。

而此时right则没有这个问题,更新right时,无论是right = mid,还是right = mid - 1,区间最终都会缩小为一个点。

如何决定mid的取整方向,我们要看题目要求,如果left right在变换mid时,left需要更新且mid值不能舍弃,right可以舍弃,此时就只能left=mid,right=mid-1,则mid求取我们就需要向上取整。

我们来看几个二分查找的经典题目。

题目链接:https://leetcode.cn/problems/guess-number-higher-or-lower

    int guessNumber(int n) {
        int left = 1, right = n;
        while (left < right) { // 循环直至区间左右端点相同
            int mid = left + (right - left) / 2; // 防止计算时溢出
            if (guess(mid) <= 0) {
                right = mid; // 答案在区间 [left, mid] 中
            } else {
                left = mid + 1; // 答案在区间 [mid+1, right] 中
            }
        }
        // 此时有 left == right,区间缩为一个点,即为答案
        return left;
    }

题目链接:https://leetcode.cn/problems/first-bad-version

    int firstBadVersion(int n) {
        int left = 1, right = n;
        while (left < right) { // 循环直至区间左右端点相同
            int mid = left + (right - left) / 2; // 防止计算时溢出
            if (isBadVersion(mid)) {
                right = mid; // 答案在区间 [left, mid] 中
            } else {
                left = mid + 1; // 答案在区间 [mid+1, right] 中
            }
        }
        // 此时有 left == right,区间缩为一个点,即为答案
        return left;
    }

再来看下这个问题,这个题目中mid要求必须向上取整。

我们使用二分查找缩短枚举甜蜜度的过程中,如果mid甜蜜度满足,此时可能存在更大的甜蜜度。

此时我们需要更新left,但是mid的值不能舍弃,因为这个值可能是目标结果,所以我们只能让left=mid。

如果mid值不满足,则right必定可以舍弃,则可以让right=mid-1。

此时,为了解决left+1=right时的死循环问题,我们只能在求取mid时向上取整,公式为 (a + b - 1) / b 。

题目链接:2517. 礼盒的最大甜蜜度

    bool check(vector<int>& price, int tastiness, int k) {
        int cnt = 1, pre = price[0];
        for (int i = 1; i < price.size(); ++i) {
            if (price[i] - pre >= tastiness) {
                pre = price[i];
                if (++cnt == k) {
                    return true;
                }
            }
        }
        return false;
    }

    int maximumTastiness(vector<int>& price, int k) {
        sort(price.begin(), price.end());
        int left = 0, right = price.back() - price.front();
        while (left < right) {
            int mid = (left + right + 1) / 2;
            if (check(price, mid, k)) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
posted @ 2023-05-20 14:35  linukey  阅读(68)  评论(0)    收藏  举报