Facebook Pixel

475. Heaters

Problem Description

You need to warm all houses using heaters with a fixed radius. Each heater can warm any house that falls within its warming radius on both sides.

You're given two arrays:

  • houses: positions of houses on a horizontal line
  • heaters: positions of heaters on a horizontal line

Your task is to find the minimum radius that all heaters must have to ensure every house is covered by at least one heater. All heaters will use the same radius value.

A house at position x is warmed by a heater at position y with radius r if the distance between them is at most r, meaning |x - y| <= r.

For example:

  • If a heater is at position 5 with radius 2, it can warm houses at positions 3, 4, 5, 6, and 7
  • If houses are at [1, 2, 3] and heaters are at [2], a radius of 1 would warm all houses since the heater at position 2 can reach houses at positions 1, 2, and 3

The solution uses binary search on the radius value combined with a greedy checking mechanism. After sorting both arrays, it searches for the smallest radius between 0 and 10^9. For each candidate radius mid, it checks if all houses can be covered by using a two-pointer technique: advancing through houses and heaters to verify if each house falls within the range [heater_position - radius, heater_position + radius] of some heater.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing this as an optimization problem where we need to find the minimum value (radius) that satisfies a condition (all houses are covered). This pattern naturally suggests binary search on the answer.

Why binary search works here? Consider that if a radius r can warm all houses, then any radius larger than r will also work. Conversely, if radius r cannot warm all houses, then any smaller radius will also fail. This creates a monotonic property: there exists a threshold radius where all values below it fail and all values at or above it succeed.

The challenge then becomes: given a specific radius, how do we efficiently check if all houses can be warmed? After sorting both arrays, we can use a greedy two-pointer approach. We process houses from left to right and for each house, we find if it can be covered by the current heater. If a house is too far to the left of the current heater (outside its range), we know this radius is insufficient. If a house is too far to the right, we move to the next heater and try again.

The sorting is crucial because it allows us to process houses and heaters in order, ensuring we don't miss any coverage gaps. Once sorted, we can confidently say that if we can't cover a house with the current or any subsequent heater, then the radius is too small.

The range for binary search [0, 10^9] is chosen to cover all possible distances since house and heater positions are typically bounded by this range in the problem constraints. We keep narrowing down this range until we find the exact minimum radius needed.

Solution Approach

The implementation consists of two main components: a binary search framework and a verification function.

Step 1: Sorting

houses.sort()
heaters.sort()

Both arrays are sorted to enable efficient checking using a two-pointer technique.

Step 2: Binary Search Setup

left, right = 0, int(1e9)

We initialize the search range where left = 0 (minimum possible radius) and right = 10^9 (maximum possible radius that covers any reasonable distance).

Step 3: Verification Function check(r) This function determines if radius r can warm all houses:

def check(r):
    m, n = len(houses), len(heaters)
    i = j = 0  # pointers for houses and heaters

The checking logic uses two pointers:

  • i tracks the current house to be checked
  • j tracks the current heater being considered

For each heater at position heaters[j], its coverage range is [heaters[j] - r, heaters[j] + r].

The verification process:

  1. If we've run out of heaters (j >= n) but still have houses to cover, return False
  2. Calculate the current heater's range: mi = heaters[j] - r and mx = heaters[j] + r
  3. If the current house is left of the heater's range (houses[i] < mi), this radius is too small, return False
  4. If the current house is right of the heater's range (houses[i] > mx), move to the next heater (j += 1)
  5. Otherwise, the house is covered, move to the next house (i += 1)

Step 4: Binary Search Execution

while left < right:
    mid = (left + right) >> 1  # equivalent to (left + right) // 2
    if check(mid):
        right = mid  # radius works, try smaller
    else:
        left = mid + 1  # radius too small, need larger

The binary search narrows down the range:

  • If check(mid) returns True, we can potentially use a smaller radius, so we set right = mid
  • If check(mid) returns False, we need a larger radius, so we set left = mid + 1

The loop continues until left == right, which gives us the minimum radius needed to cover all houses.

Time Complexity: O((m + n) log(m + n) + (m + n) log(10^9)) where m is the number of houses and n is the number of heaters. The sorting takes O((m + n) log(m + n)) and the binary search performs O(log(10^9)) iterations, each taking O(m + n) time for verification.

Space Complexity: O(1) if we don't count the sorting space, otherwise O(log(m + n)) for the sorting algorithm's recursion stack.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with houses = [1, 5, 15] and heaters = [6, 10].

Step 1: Sort both arrays

  • Houses: [1, 5, 15] (already sorted)
  • Heaters: [6, 10] (already sorted)

Step 2: Binary Search for Minimum Radius

We'll search between left = 0 and right = 1000000000.

Iteration 1:

  • mid = (0 + 1000000000) / 2 = 500000000
  • Check if radius 500000000 works:
    • Heater at position 6 covers range [-499999994, 500000006]
    • All houses (1, 5, 15) fall within this range
    • Result: TRUE
  • Since it works, try smaller: right = 500000000

Iteration 2:

  • mid = (0 + 500000000) / 2 = 250000000
  • Check if radius 250000000 works:
    • Heater at position 6 covers range [-249999994, 250000006]
    • All houses fall within this range
    • Result: TRUE
  • Try smaller: right = 250000000

[Many iterations skipped for brevity]

Key Iteration (when mid = 5):

  • mid = 5
  • Check if radius 5 works:
    • Start with house at position 1, heater at position 6
    • Heater 6 covers range [1, 11]
    • House 1 is covered (1 ≥ 1 and 1 ≤ 11) ✓
    • House 5 is covered (5 ≥ 1 and 5 ≤ 11) ✓
    • House 15 is NOT covered (15 > 11)
    • Move to next heater at position 10
    • Heater 10 covers range [5, 15]
    • House 15 is covered (15 ≥ 5 and 15 ≤ 15) ✓
    • Result: TRUE
  • Try smaller: right = 5

Key Iteration (when mid = 4):

  • mid = 4
  • Check if radius 4 works:
    • Start with house at position 1, heater at position 6
    • Heater 6 covers range [2, 10]
    • House 1 is NOT covered (1 < 2)
    • Result: FALSE
  • Need larger radius: left = 5

Final Result: When left = right = 5, we've found our answer. The minimum radius needed is 5.

Verification of the answer:

  • Heater at position 6 with radius 5 warms houses at positions 1 and 5 (range [1, 11])
  • Heater at position 10 with radius 5 warms house at position 15 (range [5, 15])
  • All houses are covered with radius 5

Solution Implementation

1class Solution:
2    def findRadius(self, houses: List[int], heaters: List[int]) -> int:
3        """
4        Find the minimum radius required for heaters to cover all houses.
5      
6        Args:
7            houses: List of house positions
8            heaters: List of heater positions
9          
10        Returns:
11            Minimum radius needed for all houses to be covered by at least one heater
12        """
13        # Sort both arrays for efficient coverage checking
14        houses.sort()
15        heaters.sort()
16      
17        def can_cover_all_houses(radius: int) -> bool:
18            """
19            Check if all houses can be covered with the given radius.
20          
21            Args:
22                radius: The heating radius to test
23              
24            Returns:
25                True if all houses can be covered, False otherwise
26            """
27            num_houses = len(houses)
28            num_heaters = len(heaters)
29            house_idx = 0
30            heater_idx = 0
31          
32            # Try to cover all houses using heaters in order
33            while house_idx < num_houses:
34                # If we've run out of heaters, we can't cover remaining houses
35                if heater_idx >= num_heaters:
36                    return False
37              
38                # Calculate the coverage range of current heater
39                heater_min_coverage = heaters[heater_idx] - radius
40                heater_max_coverage = heaters[heater_idx] + radius
41              
42                # If current house is before the heater's coverage range
43                if houses[house_idx] < heater_min_coverage:
44                    return False
45              
46                # If current house is beyond the heater's coverage range
47                if houses[house_idx] > heater_max_coverage:
48                    # Move to next heater
49                    heater_idx += 1
50                else:
51                    # Current house is covered, move to next house
52                    house_idx += 1
53          
54            return True
55      
56        # Binary search for the minimum radius
57        min_radius = 0
58        max_radius = int(1e9)
59      
60        while min_radius < max_radius:
61            # Calculate middle value
62            mid_radius = (min_radius + max_radius) >> 1
63          
64            # If current radius works, try smaller radius
65            if can_cover_all_houses(mid_radius):
66                max_radius = mid_radius
67            else:
68                # If current radius doesn't work, need larger radius
69                min_radius = mid_radius + 1
70      
71        return min_radius
72
1class Solution {
2    public int findRadius(int[] houses, int[] heaters) {
3        // Sort heaters array to enable binary search
4        Arrays.sort(heaters);
5      
6        // Track the minimum radius needed to cover all houses
7        int minRadius = 0;
8      
9        // For each house, find the minimum distance to nearest heater
10        for (int housePosition : houses) {
11            // Binary search for the house position in heaters array
12            // Returns index if found, or negative insertion point - 1 if not found
13            int searchResult = Arrays.binarySearch(heaters, housePosition);
14          
15            // If house position not found in heaters, convert to insertion point
16            // The bitwise NOT (~) converts negative result to the insertion index
17            if (searchResult < 0) {
18                searchResult = ~searchResult;
19            }
20          
21            // Calculate distance to the left heater (if exists)
22            // If no left heater, set distance to MAX_VALUE
23            int distanceToLeftHeater = (searchResult > 0) 
24                ? housePosition - heaters[searchResult - 1] 
25                : Integer.MAX_VALUE;
26          
27            // Calculate distance to the right heater (if exists)
28            // If no right heater, set distance to MAX_VALUE
29            int distanceToRightHeater = (searchResult < heaters.length) 
30                ? heaters[searchResult] - housePosition 
31                : Integer.MAX_VALUE;
32          
33            // The minimum distance to cover this house is the closer heater
34            // Update the overall minimum radius to be the maximum of all minimum distances
35            minRadius = Math.max(minRadius, Math.min(distanceToLeftHeater, distanceToRightHeater));
36        }
37      
38        return minRadius;
39    }
40}
41
1class Solution {
2public:
3    int findRadius(vector<int>& houses, vector<int>& heaters) {
4        // Sort both arrays to enable efficient checking
5        sort(houses.begin(), houses.end());
6        sort(heaters.begin(), heaters.end());
7      
8        // Binary search on the radius value
9        // Range: [0, 10^9] covers all possible distances
10        int minRadius = 0;
11        int maxRadius = 1000000000;
12      
13        while (minRadius < maxRadius) {
14            // Calculate middle radius value
15            int midRadius = minRadius + (maxRadius - minRadius) / 2;
16          
17            // Check if current radius can cover all houses
18            if (canCoverAllHouses(houses, heaters, midRadius)) {
19                // If yes, try to find a smaller radius
20                maxRadius = midRadius;
21            } else {
22                // If no, we need a larger radius
23                minRadius = midRadius + 1;
24            }
25        }
26      
27        return minRadius;
28    }
29
30private:
31    bool canCoverAllHouses(vector<int>& houses, vector<int>& heaters, int radius) {
32        int numHouses = houses.size();
33        int numHeaters = heaters.size();
34      
35        // Two pointers: houseIndex for houses, heaterIndex for heaters
36        int houseIndex = 0;
37        int heaterIndex = 0;
38      
39        // Try to cover all houses with the given radius
40        while (houseIndex < numHouses) {
41            // No more heaters available to cover remaining houses
42            if (heaterIndex >= numHeaters) {
43                return false;
44            }
45          
46            // Calculate the coverage range of current heater
47            int heaterMinCoverage = heaters[heaterIndex] - radius;
48            int heaterMaxCoverage = heaters[heaterIndex] + radius;
49          
50            // Current house is before the heater's coverage range
51            if (houses[houseIndex] < heaterMinCoverage) {
52                return false;
53            }
54          
55            // Current house is beyond the heater's coverage range
56            if (houses[houseIndex] > heaterMaxCoverage) {
57                // Move to next heater
58                ++heaterIndex;
59            } else {
60                // Current house is covered, move to next house
61                ++houseIndex;
62            }
63        }
64      
65        // All houses are covered
66        return true;
67    }
68};
69
1/**
2 * Finds the minimum radius required for heaters to warm all houses.
3 * Each heater can warm houses within its radius range.
4 * 
5 * @param houses - Array of house positions on a number line
6 * @param heaters - Array of heater positions on a number line
7 * @returns The minimum radius needed to warm all houses
8 */
9function findRadius(houses: number[], heaters: number[]): number {
10    // Sort both arrays to enable efficient searching
11    houses.sort((a, b) => a - b);
12    heaters.sort((a, b) => a - b);
13  
14    const houseCount = houses.length;
15    const heaterCount = heaters.length;
16    let maxRadius = 0;
17  
18    // For each house, find the closest heater
19    for (let houseIndex = 0, heaterIndex = 0; houseIndex < houseCount; houseIndex++) {
20        // Calculate distance to current heater
21        let minDistanceToHeater = Math.abs(houses[houseIndex] - heaters[heaterIndex]);
22      
23        // Check if next heater is closer to current house
24        // Keep moving to next heater while it's closer or equally close
25        while (
26            heaterIndex + 1 < heaterCount &&
27            Math.abs(houses[houseIndex] - heaters[heaterIndex]) >= 
28            Math.abs(houses[houseIndex] - heaters[heaterIndex + 1])
29        ) {
30            heaterIndex++;
31            minDistanceToHeater = Math.min(
32                Math.abs(houses[houseIndex] - heaters[heaterIndex]), 
33                minDistanceToHeater
34            );
35        }
36      
37        // Update maximum radius needed (the worst case among all houses)
38        maxRadius = Math.max(minDistanceToHeater, maxRadius);
39    }
40  
41    return maxRadius;
42}
43

Time and Space Complexity

Time Complexity: O((m + n) * log(m + n) + (m + n) * log(max_distance))

  • Sorting houses takes O(m * log(m)) where m is the number of houses
  • Sorting heaters takes O(n * log(n)) where n is the number of heaters
  • Binary search runs for O(log(max_distance)) iterations, where max_distance is the search space from 0 to 10^9
  • Each binary search iteration calls the check() function, which takes O(m + n) time in the worst case as it iterates through all houses and potentially all heaters
  • Overall: O(m * log(m) + n * log(n) + (m + n) * log(10^9))
  • Since log(10^9) ≈ 30 is a constant, and typically m + n dominates the sorting complexity, this simplifies to O((m + n) * log(m + n) + (m + n) * log(max_distance))

Space Complexity: O(1)

  • The sorting operations are done in-place (Python's sort uses Timsort which requires O(min(m, n)) auxiliary space in worst case, but typically O(1) for already sorted or small arrays)
  • The algorithm only uses a constant amount of extra variables (i, j, left, right, mid, mi, mx, r)
  • No additional data structures are created
  • Therefore, the space complexity is O(1) excluding the space used by sorting

Common Pitfalls

1. Incorrect Coverage Check Logic

A common mistake is incorrectly implementing the coverage verification logic, particularly when determining whether to move to the next heater or declare failure.

Pitfall Example:

# WRONG: This logic fails to properly handle all cases
def can_cover_all_houses(radius):
    house_idx = 0
    for heater in heaters:
        while house_idx < len(houses) and houses[house_idx] <= heater + radius:
            house_idx += 1
    return house_idx == len(houses)

This fails because it doesn't check if houses before the heater's minimum coverage are actually covered.

Solution: Ensure you check both boundaries of the heater's coverage range:

if houses[house_idx] < heater_min_coverage:
    return False  # House is too far left
if houses[house_idx] > heater_max_coverage:
    heater_idx += 1  # Try next heater
else:
    house_idx += 1  # House is covered

2. Forgetting to Sort Arrays

The two-pointer technique relies on sorted arrays. Without sorting, the algorithm won't work correctly.

Pitfall Example:

# WRONG: Directly using unsorted arrays
def findRadius(self, houses, heaters):
    # Missing: houses.sort() and heaters.sort()
    left, right = 0, int(1e9)
    # ... rest of the code

Solution: Always sort both arrays before applying the binary search:

houses.sort()
heaters.sort()

3. Off-by-One Error in Binary Search

Using the wrong binary search template can lead to infinite loops or incorrect results.

Pitfall Example:

# WRONG: This can cause infinite loop
while left <= right:  # Should be left < right
    mid = (left + right) // 2
    if check(mid):
        right = mid - 1  # Should be right = mid
    else:
        left = mid + 1

Solution: Use the correct binary search template for finding minimum value:

while left < right:
    mid = (left + right) >> 1
    if check(mid):
        right = mid  # Keep mid as potential answer
    else:
        left = mid + 1

4. Integer Overflow in Binary Search

While less common in Python, calculating mid as (left + right) // 2 can theoretically cause issues with very large numbers.

Pitfall Example:

# Potentially problematic for extremely large values
mid = (left + right) // 2

Solution: Use bit shifting or a safer calculation:

mid = (left + right) >> 1
# OR
mid = left + (right - left) // 2

5. Inefficient Initial Search Range

Setting an unnecessarily large upper bound for the binary search can slow down the algorithm.

Pitfall Example:

# WRONG: Using arbitrary large number
right = int(1e15)  # Too large, wastes iterations

Solution: Use a reasonable upper bound or calculate based on the actual data:

# Option 1: Use reasonable maximum
right = int(1e9)

# Option 2: Calculate based on actual positions
right = max(max(houses) - min(heaters), max(heaters) - min(houses))

6. Not Handling Edge Cases

Failing to consider edge cases like empty arrays or single elements.

Pitfall Example:

# WRONG: No edge case handling
def findRadius(self, houses, heaters):
    houses.sort()
    heaters.sort()
    # Directly proceed without checking if arrays are empty

Solution: Add edge case checks:

if not houses:
    return 0
if not heaters:
    return float('inf')  # Or handle appropriately
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".

What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More