二分(二)供暖器

对应 LeetCode 475.供暖器

问题描述

给出位于一条水平线上的房屋 \(houses\) 和供暖器 \(heaters\) 的位置,请找出并返回可以覆盖所有房屋的最小加热半径。所有供暖器都遵循你的半径标准,加热的半径也一样

比如,对于输入的 \(house=[1, 5]\)​,\(heaters=[2]\)​,那么至少需要使得供暖器的最小覆盖半径为 3 才能使得所有\(house\)​​​ 都能够被供暖器覆盖

数据范围:

  • \(1 <= houses.length, heaters.length <= 3 * 10^4\)

  • \(1 <= houses[i], heaters[i] <= 10^9\)

解决思路

由题目描述可知,本题具备的二分性:

  • 如果供暖器的覆盖半径足够大,那么肯定能够将所有的房子都覆盖
  • 如果供暖器的半径过小,那么肯定存在房子无法被覆盖

可以考虑通过二分的方式来求得最小的覆盖半径。二分的最小值为 0,即每个房子都有自己的供暖器,此时就不再需要覆盖半径;二分的最大值为 \(houses\) 的最大值,为了简单起见,可以将最大值设为 \(10^9\)

现在关键的地方在于如何检查给定的半径是否能够覆盖所有的房子。首先,需要对 \(houses\)\(heaters\) 进行排序,然后再遍历 \(houses\) 的每个元素,找到在当前半径的条件下能够覆盖该 \(house\) 的最小的供暖器,如果无法找到,那么说明无法覆盖该 \(house\)

实现

具体的实现如下:

class Solution {
    public int findRadius(int[] houses, int[] heaters) {
        Arrays.sort(houses);
        Arrays.sort(heaters);

        int lo = 0, hi = (int) 1e9;
        // 注意二分的边界情况,这是一个细节性的问题
        while (lo < hi) {
            int mid = lo + ((hi - lo) >> 1);
            if (check(houses, heaters, mid)) hi = mid;
            else lo = mid + 1;
        }

        return hi;
    }

    boolean check(int[] houses, int[] heaters, int x) {
        int n = houses.length, m = heaters.length;
        /*
        	通过双指针的方式首先找到合适的供暖器 heaters[j],
        	然后再使用当前的覆盖半径检测是否能够覆盖该房子
        */
        for (int i = 0, j = 0; i < n; ++i) {
            while (j < m && heaters[j] + x < houses[i]) ++j;
            // j >= m 说明没有供暖器了
            if (j < m && houses[i] >= heaters[j] - x && houses[i] <= heaters[j] + x) 
                continue;
            return false;
        }

        return true;
    }
}

时间复杂度:令 \(n=house.length, m = heaters.length\),排序带来 \(O(nlog_2n) + O(mlog_2m)\) 的时间复杂度,使用二分搜索带来的时间复杂度为 \(O(nlog_210^9)\) 总体时间复杂度为 \(O(max(n, m)*log_210^9)\)

空间复杂度:具体由排序算法带来的空间复杂度,在这里不做分析

参考:https://leetcode-cn.com/problems/heaters/solution/gong-shui-san-xie-er-fen-shuang-zhi-zhen-mys4/

posted @ 2021-12-20 09:51  FatalFlower  阅读(26)  评论(0编辑  收藏  举报