Leetcode 475: Heaters

题目:



  这道题目要求我们找到加热器的最小半径,使得所有的房子houses都可以被这些加热器heaters加热到,也就是寻找满足一定条件的某个数,因此可以用二分查找解这个题。
Step1:找到查找范围,半径最小值当然是0(比如加热器和房子的位置在一起),最大的半径就是houses的最大值和heaters的最大值中更大的那个值,也就是房子和加热器的最远坐标,半径再大下去肯定是满足条件但不是最小值。
Step2:确定check()函数,也就是对于当前查找的值,如何判断它是否满足我们想找的数的条件。由于真的太憨了,所以先用了一个非常暴力的方法,就是去遍历每个房子,如果在当前的半径下不是每个房子都可以被加热到,说明当前的半径太小了,需要在[mid+1, hi]之间继续查找;如果当前的半径下所有房子都可以被加热到,说明当前的半径mid是满足条件的,但不一定是最小值,因此在[lo, mid]之间继续查找。代码如下:

class Solution:
    def findRadius(self, houses: List[int], heaters: List[int]) -> int:
        houses.sort(); heaters.sort()
        n, m = len(houses), len(heaters)
        lo, hi = 0, max([max(houses), max(heaters)])
        def check(mid):
            flag = False
            for i in range(0, n):
                flag_tmp = True
                # 对于某一个房子,如果它到所有加热器的距离都大于加热器的半径的话,就说明该房子不能被加热到
                for j in range(0, m):
                    flag_tmp = flag_tmp and (abs(houses[i] - heaters[j]) > mid)
                flag = flag or flag_tmp
            print(mid, flag)
            return flag
        while lo < hi:
            mid = lo + (hi - lo) // 2
            if check(mid):
                lo = mid + 1
            else:
                hi = mid
        return lo

果不其然,check()函数过于复杂,超时了。。。
下面的代码把check()函数修改了一下(其实是某憨猪的代码),不用两层遍历,而是通过双指针来判断当前半径是否满足条件(有一点点绕,需要好好理解一下),这样check()函数的复杂度从n*m变成了n+m,代码如下:

class Solution:
    def findRadius(self, houses: List[int], heaters: List[int]) -> int:
        houses.sort()
        heaters.sort()
        n, m = len(houses), len(heaters)
        lo, hi = 0, max([abs(houses[-1]-heaters[0]), abs(heaters[-1]-houses[0])])

        def check(mid):
            i = j = 0
            while i < n:
                while j < m and abs(houses[i] - heaters[j]) > mid:  # 看第j个加热器是否可以覆盖到第i个房子,
                                                     #若不可以,则判断下一个加热器j+1是否可以覆盖第i个房子;
                                                     #若可以覆盖到第i个房子,则看加热器j可不可以覆盖第i+1个房子。
                    j += 1
                if j == m: return True  # 若heaters的指针先遍历完,那么说明对于某一个房子而言,所有的加热器都不能覆盖到它,也就是当前半径mid不满足条件,应该增大搜索半径。
                i += 1
            return False

        while lo < hi:
            mid = lo + (hi - lo) // 2
            if check(mid):
                lo = mid + 1
            else:
                hi = mid
        return lo

此外,这道题可以不用这个标准的二分的模板,这道题是需要求使得所有房子都能被加热的最小半径,因此一个比较直接的思路是:对于每一个房子,我们去找它与离它最近的加热器的距离,因此我们所找的半径是必须大于所有房子与离它最近的加热器的距离的。我们对于每个房子找到它对应的距离,最终所求的加热器最小半径就是这些距离的最大值。当我们在找每个房子与它最近的加热器时,可以用二分查找实现,这里用自带的函数bisect()函数实现。代码如下:

class Solution:
    def findRadius4(self, houses: List[int], heaters: List[int]) -> int:
        houses.sort()
        heaters.sort()
        radius = 0
        for h in houses:
            idx = bisect.bisect(heaters, h)  # 讲当前这个房子h插入到heaters中时的下标
            print(idx)
            if idx == 0:
                radius = max([radius, abs(h-heaters[idx])])  # 距离是abs(h-heaters[idx])
            elif idx == len(heaters):
                radius = max([radius, abs(h - heaters[idx-1])])  # 距离是abs(h-heaters[idx-1])
            else:
                radius = max([radius, min([abs(h - heaters[idx - 1]), abs(h - heaters[idx])])])  # 距离是min([abs(h - heaters[idx - 1]), abs(h - heaters[idx])])
        return radius

整个算法复杂度是O(nlogm),果不其然,快了很多,说明模板很好用,但是有的时候也需要根据题意变换一下。

你可能感兴趣的:(Leetcode 475: Heaters)