二分(二)供暖器
问题描述
给出位于一条水平线上的房屋 \(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/