2560. House Robber IV
Problem Description
You have a row of consecutive houses along a street, where each house contains some money. A robber wants to steal from these houses, but has one strict rule: he cannot steal from two adjacent houses.
Given:
- An integer array
nums
wherenums[i]
represents the amount of money in thei
-th house - An integer
k
representing the minimum number of houses the robber must steal from
The capability of the robber is defined as the maximum amount of money he takes from any single house among all the houses he robs.
Your task is to find the minimum possible capability that allows the robber to steal from at least k
houses while never robbing adjacent houses.
For example, if nums = [2, 3, 5, 9]
and k = 2
:
- If the robber steals from houses with values 2 and 5, his capability is
max(2, 5) = 5
- If the robber steals from houses with values 2 and 9, his capability is
max(2, 9) = 9
- If the robber steals from houses with values 3 and 9, his capability is
max(3, 9) = 9
The minimum capability would be 5 in this case.
The problem guarantees it's always possible to steal from at least k
houses following the non-adjacent rule.
Intuition
The key insight is recognizing that this is an optimization problem where we need to find the minimum value of something (capability) that satisfies a constraint (robbing at least k
houses).
Think about it this way: if we fix a capability value x
, we can ask ourselves - "Can the robber steal from at least k
houses without exceeding this capability?" This becomes a yes/no question. If the answer is yes, we might be able to do better with a smaller capability. If the answer is no, we need a larger capability.
This binary nature of the problem (yes/no for each capability value) suggests using binary search. The capability must be between the minimum and maximum values in the array. We can search this range to find the smallest capability that works.
For a given capability x
, how do we check if it's possible to rob k
houses? We want to maximize the number of houses we can rob while staying within capability x
. A greedy approach works here: go through the houses from left to right, and whenever we see a house with value ≤ x
that isn't adjacent to the last house we robbed, we should rob it. Why? Because robbing it now doesn't prevent us from robbing better houses later - we're just trying to maximize the count of houses robbed.
The beauty of this approach is that binary search naturally finds the minimum capability. As we search, we keep lowering our capability threshold whenever we find it's possible to rob k
houses, eventually converging on the minimum possible value.
Learn more about Greedy, Binary Search and Dynamic Programming patterns.
Solution Approach
The solution combines binary search with a greedy validation function to find the minimum capability.
Binary Search Setup:
We search for the minimum capability in the range [0, max(nums)]
. The capability cannot exceed the maximum value in the array since that's the most money in any single house.
Validation Function f(x)
:
For a given capability x
, we check if the robber can steal from at least k
houses:
- Initialize a counter
cnt = 0
for houses robbed andj = -2
to track the index of the last robbed house (starting at-2
ensures the first house can always be considered) - Iterate through each house
i
with valuev
:- Skip this house if
v > x
(exceeds our capability) or ifi == j + 1
(adjacent to last robbed house) - Otherwise, rob this house: increment
cnt
and updatej = i
- Skip this house if
- Return
True
ifcnt >= k
, meaning we can rob enough houses with this capability
Binary Search Execution:
The code uses bisect_left(range(max(nums) + 1), True, key=f)
which:
- Searches the range
[0, max(nums)]
- Looks for the leftmost (minimum) value where
f(x)
returnsTrue
- This gives us the minimum capability that allows robbing at least
k
houses
Why This Works:
- If capability
x
allows robbingk
houses, then any capability> x
also works (monotonic property) - Binary search efficiently finds the boundary between "not possible" and "possible"
- The greedy approach in validation is optimal because we want to maximize the count of houses robbed for a given capability, and taking any valid house early doesn't prevent us from taking more houses later
Time Complexity: O(n * log(max(nums)))
where n
is the length of the array
Space Complexity: O(1)
as we only use a few variables
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 the solution with nums = [2, 7, 9, 3, 1]
and k = 2
.
Step 1: Set up binary search range
- Minimum capability: 0
- Maximum capability: max(nums) = 9
- We'll search in range [0, 9] for the minimum capability that allows robbing at least 2 houses
Step 2: Binary search process
First iteration: mid = 4
- Check if capability 4 allows robbing 2 houses
- Go through houses:
- House 0 (value=2): 2 ≤ 4, rob it! Count = 1, last robbed index = 0
- House 1 (value=7): 7 > 4, skip (exceeds capability)
- House 2 (value=9): 9 > 4, skip (exceeds capability)
- House 3 (value=3): 3 ≤ 4 and not adjacent to house 0, rob it! Count = 2
- Count = 2 ≥ k = 2 ✓
- Since it works, try smaller: search [0, 4]
Second iteration: mid = 2
- Check if capability 2 allows robbing 2 houses
- Go through houses:
- House 0 (value=2): 2 ≤ 2, rob it! Count = 1, last robbed index = 0
- House 1 (value=7): 7 > 2, skip (exceeds capability)
- House 2 (value=9): 9 > 2, skip (exceeds capability)
- House 3 (value=3): 3 > 2, skip (exceeds capability)
- House 4 (value=1): 1 ≤ 2 and not adjacent to house 0, rob it! Count = 2
- Count = 2 ≥ k = 2 ✓
- Since it works, try smaller: search [0, 2]
Third iteration: mid = 1
- Check if capability 1 allows robbing 2 houses
- Go through houses:
- House 0 (value=2): 2 > 1, skip (exceeds capability)
- House 1 (value=7): 7 > 1, skip (exceeds capability)
- House 2 (value=9): 9 > 1, skip (exceeds capability)
- House 3 (value=3): 3 > 1, skip (exceeds capability)
- House 4 (value=1): 1 ≤ 1, rob it! Count = 1
- Count = 1 < k = 2 ✗
- Doesn't work, need larger: search [2, 2]
Final result: minimum capability = 2
The robber can steal from houses with values [2, 1] (indices 0 and 4) with a capability of 2, which is the minimum possible capability to rob at least 2 houses.
Solution Implementation
1class Solution:
2 def minCapability(self, nums: List[int], k: int) -> int:
3 def can_rob_k_houses(max_value):
4 """
5 Check if we can rob at least k houses with given maximum value constraint.
6 We cannot rob adjacent houses (must skip at least one house between robberies).
7
8 Args:
9 max_value: Maximum value we're allowed to take from any house
10
11 Returns:
12 True if we can rob at least k houses, False otherwise
13 """
14 robbed_count = 0
15 last_robbed_index = -2 # Initialize to -2 so first house at index 0 can be robbed
16
17 for current_index, house_value in enumerate(nums):
18 # Skip if house value exceeds our limit or if it's adjacent to last robbed house
19 if house_value > max_value or current_index == last_robbed_index + 1:
20 continue
21
22 # Rob this house
23 robbed_count += 1
24 last_robbed_index = current_index
25
26 return robbed_count >= k
27
28 # Binary search for minimum capability that allows robbing k houses
29 # Search range is from 0 to max(nums)
30 return bisect_left(range(max(nums) + 1), True, key=can_rob_k_houses)
31
1class Solution {
2 /**
3 * Finds the minimum capability needed to rob at least k houses
4 * @param nums Array representing the money in each house
5 * @param k Minimum number of houses that need to be robbed
6 * @return The minimum capability required
7 */
8 public int minCapability(int[] nums, int k) {
9 // Binary search range: minimum capability is 0, maximum is 10^9
10 int left = 0;
11 int right = (int) 1e9;
12
13 // Binary search to find the minimum capability
14 while (left < right) {
15 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
16
17 // Check if with capability 'mid', we can rob at least k houses
18 if (countRobbableHouses(nums, mid) >= k) {
19 // If we can rob k or more houses, try a smaller capability
20 right = mid;
21 } else {
22 // If we can't rob k houses, we need higher capability
23 left = mid + 1;
24 }
25 }
26
27 return left;
28 }
29
30 /**
31 * Counts the maximum number of houses that can be robbed with given capability
32 * Adjacent houses cannot be robbed (security constraint)
33 * @param nums Array representing the money in each house
34 * @param maxCapability The maximum amount that can be stolen from a single house
35 * @return The maximum number of houses that can be robbed
36 */
37 private int countRobbableHouses(int[] nums, int maxCapability) {
38 int robbedCount = 0;
39 int lastRobbedIndex = -2; // Initialize to -2 so first house at index 0 can be robbed
40
41 for (int currentIndex = 0; currentIndex < nums.length; ++currentIndex) {
42 // Skip if house value exceeds capability or if it's adjacent to last robbed house
43 if (nums[currentIndex] > maxCapability || currentIndex == lastRobbedIndex + 1) {
44 continue;
45 }
46
47 // Rob this house
48 ++robbedCount;
49 lastRobbedIndex = currentIndex;
50 }
51
52 return robbedCount;
53 }
54}
55
1class Solution {
2public:
3 int minCapability(vector<int>& nums, int k) {
4 // Lambda function to check if we can rob at least k houses with max capability of maxCapability
5 auto canRobKHouses = [&](int maxCapability) {
6 int robbedCount = 0; // Number of houses we can rob
7 int lastRobbedIndex = -2; // Index of last robbed house (initialized to -2 to allow robbing house at index 0)
8
9 for (int i = 0; i < nums.size(); ++i) {
10 // Skip if current house value exceeds our capability limit
11 // or if current house is adjacent to the last robbed house
12 if (nums[i] > maxCapability || i == lastRobbedIndex + 1) {
13 continue;
14 }
15
16 // Rob current house
17 ++robbedCount;
18 lastRobbedIndex = i;
19 }
20
21 // Check if we can rob at least k houses
22 return robbedCount >= k;
23 };
24
25 // Binary search on the answer (minimum capability needed)
26 int left = 0;
27 int right = *max_element(nums.begin(), nums.end());
28
29 while (left < right) {
30 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
31
32 if (canRobKHouses(mid)) {
33 // If we can rob k houses with capability mid, try smaller capability
34 right = mid;
35 } else {
36 // If we cannot rob k houses with capability mid, need larger capability
37 left = mid + 1;
38 }
39 }
40
41 return left;
42 }
43};
44
1/**
2 * Finds the minimum capability required to rob at least k houses
3 * where houses cannot be robbed consecutively
4 * @param nums - Array of values representing each house
5 * @param k - Minimum number of houses to rob
6 * @returns The minimum maximum value among selected houses
7 */
8function minCapability(nums: number[], k: number): number {
9 /**
10 * Checks if it's possible to rob at least k houses with maximum value <= maxValue
11 * @param maxValue - The maximum allowed value for any house to rob
12 * @returns True if we can rob at least k houses, false otherwise
13 */
14 const canRobWithCapability = (maxValue: number): boolean => {
15 let robbedCount = 0;
16 let lastRobbedIndex = -2; // Initialize to -2 to allow robbing the first house
17
18 // Iterate through all houses
19 for (let currentIndex = 0; currentIndex < nums.length; currentIndex++) {
20 // Check if current house value is within capability and not adjacent to last robbed
21 if (nums[currentIndex] <= maxValue && currentIndex - lastRobbedIndex > 1) {
22 robbedCount++;
23 lastRobbedIndex = currentIndex;
24 }
25 }
26
27 // Check if we can rob at least k houses
28 return robbedCount >= k;
29 };
30
31 // Binary search bounds: minimum is 1, maximum is the highest house value
32 let leftBound = 1;
33 let rightBound = Math.max(...nums);
34
35 // Binary search to find the minimum capability
36 while (leftBound < rightBound) {
37 const midPoint = (leftBound + rightBound) >> 1; // Equivalent to Math.floor((left + right) / 2)
38
39 if (canRobWithCapability(midPoint)) {
40 // If we can rob k houses with this capability, try a smaller one
41 rightBound = midPoint;
42 } else {
43 // If we cannot rob k houses with this capability, we need a larger one
44 leftBound = midPoint + 1;
45 }
46 }
47
48 return leftBound;
49}
50
Time and Space Complexity
Time Complexity: O(n × log m)
The algorithm uses binary search on the range [0, max(nums)]
to find the minimum capability. The bisect_left
function performs binary search, which takes O(log m)
iterations where m = max(nums)
. For each iteration of the binary search, the helper function f(x)
is called, which iterates through all n
elements of the nums
array once, taking O(n)
time. Therefore, the overall time complexity is O(n × log m)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space. The helper function f(x)
uses two variables cnt
and j
to track the count and the last selected index. The binary search itself doesn't require additional space proportional to the input size. Therefore, the space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Greedy Strategy in Validation Function
The Pitfall: A common mistake is using a different greedy strategy that seems intuitive but doesn't maximize the number of houses robbed. For example:
- Trying to minimize gaps between robbed houses
- Starting from houses with smaller values first
- Using dynamic programming instead of greedy
Wrong Approach Example:
def can_rob_k_houses_wrong(max_value):
# Wrong: Trying to be "smart" about which houses to skip
robbed = []
for i, v in enumerate(nums):
if v <= max_value:
if not robbed or i > robbed[-1] + 1:
robbed.append(i)
elif len(robbed) >= 2 and i > robbed[-2] + 2:
# Wrong logic: trying to optimize placement
robbed[-1] = i
return len(robbed) >= k
The Solution: Stick to the simple greedy approach: take any valid house as soon as you can. This maximizes the count because:
- Taking a house early never prevents you from taking more houses later
- Skipping a valid house might force you to skip more houses due to adjacency constraints
2. Off-by-One Error in Binary Search Range
The Pitfall:
Using range(max(nums))
instead of range(max(nums) + 1)
in the binary search. This would miss the case where the minimum capability equals the maximum value in the array.
Wrong Code:
# This misses the case where answer = max(nums)
return bisect_left(range(max(nums)), True, key=can_rob_k_houses)
The Solution: Always include the upper bound in the search range:
return bisect_left(range(max(nums) + 1), True, key=can_rob_k_houses)
3. Incorrect Initial Value for Last Robbed Index
The Pitfall:
Initializing last_robbed_index
to -1
instead of -2
. This would incorrectly prevent robbing the house at index 0 when checking current_index == last_robbed_index + 1
.
Wrong Code:
last_robbed_index = -1 # Wrong: 0 == (-1) + 1, so house 0 would be skipped
The Solution:
Initialize to -2
or any value less than -1
:
last_robbed_index = -2 # Correct: ensures house at index 0 can be robbed
4. Using bisect_right Instead of bisect_left
The Pitfall:
Using bisect_right
would find the rightmost position where True
can be inserted, giving us a value one higher than the minimum capability.
Wrong Code:
# This returns minimum_capability + 1
return bisect_right(range(max(nums) + 1), True, key=can_rob_k_houses)
The Solution:
Use bisect_left
to find the leftmost (minimum) value where the condition is satisfied:
return bisect_left(range(max(nums) + 1), True, key=can_rob_k_houses)
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!