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 lineheaters
: 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.
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 checkedj
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:
- If we've run out of heaters (
j >= n
) but still have houses to cover, returnFalse
- Calculate the current heater's range:
mi = heaters[j] - r
andmx = heaters[j] + r
- If the current house is left of the heater's range (
houses[i] < mi
), this radius is too small, returnFalse
- If the current house is right of the heater's range (
houses[i] > mx
), move to the next heater (j += 1
) - 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)
returnsTrue
, we can potentially use a smaller radius, so we setright = mid
- If
check(mid)
returnsFalse
, we need a larger radius, so we setleft = 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 EvaluatorExample 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
- Heater at position 6 covers range
- 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
- Heater at position 6 covers range
- 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))
wherem
is the number of houses - Sorting heaters takes
O(n * log(n))
wheren
is the number of heaters - Binary search runs for
O(log(max_distance))
iterations, wheremax_distance
is the search space from 0 to10^9
- Each binary search iteration calls the
check()
function, which takesO(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 typicallym + n
dominates the sorting complexity, this simplifies toO((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 typicallyO(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
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
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!