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
numswherenums[i]represents the amount of money in thei-th house - An integer
krepresenting 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 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 robbed_count = 0
9 last_robbed_index = -2 # Initialize to -2 so first house at index 0 can be robbed
10
11 for current_index, house_value in enumerate(nums):
12 # Skip if house value exceeds our limit or if it's adjacent to last robbed house
13 if house_value > max_value or current_index == last_robbed_index + 1:
14 continue
15
16 # Rob this house
17 robbed_count += 1
18 last_robbed_index = current_index
19
20 return robbed_count >= k
21
22 # Binary search for minimum capability that allows robbing k houses
23 # Feasible condition: can_rob_k_houses(mid) returns True
24 # We want the FIRST capability where we can rob k houses
25 left, right = 0, max(nums)
26 first_true_index = -1
27
28 while left <= right:
29 mid = (left + right) // 2
30 if can_rob_k_houses(mid):
31 first_true_index = mid
32 right = mid - 1
33 else:
34 left = mid + 1
35
36 return first_true_index
371class Solution {
2 public int minCapability(int[] nums, int k) {
3 // Binary search for minimum capability
4 // Feasible condition: can rob at least k houses with this capability
5 int left = 0;
6 int right = (int) 1e9;
7 int firstTrueIndex = -1;
8
9 while (left <= right) {
10 int mid = left + (right - left) / 2;
11
12 if (countRobbableHouses(nums, mid) >= k) {
13 // Can rob k houses - this capability is feasible
14 firstTrueIndex = mid;
15 right = mid - 1;
16 } else {
17 // Cannot rob k houses - need higher capability
18 left = mid + 1;
19 }
20 }
21
22 return firstTrueIndex;
23 }
24
25 private int countRobbableHouses(int[] nums, int maxCapability) {
26 int robbedCount = 0;
27 int lastRobbedIndex = -2; // Initialize to -2 so first house at index 0 can be robbed
28
29 for (int currentIndex = 0; currentIndex < nums.length; ++currentIndex) {
30 // Skip if house value exceeds capability or if it's adjacent to last robbed house
31 if (nums[currentIndex] > maxCapability || currentIndex == lastRobbedIndex + 1) {
32 continue;
33 }
34
35 // Rob this house
36 ++robbedCount;
37 lastRobbedIndex = currentIndex;
38 }
39
40 return robbedCount;
41 }
42}
431class Solution {
2public:
3 int minCapability(vector<int>& nums, int k) {
4 // Lambda function to check if we can rob at least k houses
5 auto canRobKHouses = [&](int maxCapability) {
6 int robbedCount = 0;
7 int lastRobbedIndex = -2;
8
9 for (int i = 0; i < nums.size(); ++i) {
10 if (nums[i] > maxCapability || i == lastRobbedIndex + 1) {
11 continue;
12 }
13 ++robbedCount;
14 lastRobbedIndex = i;
15 }
16
17 return robbedCount >= k;
18 };
19
20 // Binary search for minimum capability
21 // Feasible condition: can rob at least k houses
22 int left = 0;
23 int right = *max_element(nums.begin(), nums.end());
24 int firstTrueIndex = -1;
25
26 while (left <= right) {
27 int mid = left + (right - left) / 2;
28
29 if (canRobKHouses(mid)) {
30 // Can rob k houses - this capability is feasible
31 firstTrueIndex = mid;
32 right = mid - 1;
33 } else {
34 // Cannot rob k houses - need higher capability
35 left = mid + 1;
36 }
37 }
38
39 return firstTrueIndex;
40 }
41};
421function minCapability(nums: number[], k: number): number {
2 const canRobWithCapability = (maxValue: number): boolean => {
3 let robbedCount = 0;
4 let lastRobbedIndex = -2;
5
6 for (let currentIndex = 0; currentIndex < nums.length; currentIndex++) {
7 if (nums[currentIndex] <= maxValue && currentIndex - lastRobbedIndex > 1) {
8 robbedCount++;
9 lastRobbedIndex = currentIndex;
10 }
11 }
12
13 return robbedCount >= k;
14 };
15
16 // Binary search for minimum capability
17 // Feasible condition: can rob at least k houses
18 let left = 1;
19 let right = Math.max(...nums);
20 let firstTrueIndex = -1;
21
22 while (left <= right) {
23 const mid = Math.floor((left + right) / 2);
24
25 if (canRobWithCapability(mid)) {
26 // Can rob k houses - this capability is feasible
27 firstTrueIndex = mid;
28 right = mid - 1;
29 } else {
30 // Cannot rob k houses - need higher capability
31 left = mid + 1;
32 }
33 }
34
35 return firstTrueIndex;
36}
37Solution 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 = 0for houses robbed andj = -2to track the index of the last robbed house (starting at-2ensures the first house can always be considered) - Iterate through each house
iwith valuev:- Skip this house if
v > x(exceeds our capability) or ifi == j + 1(adjacent to last robbed house) - Otherwise, rob this house: increment
cntand updatej = i
- Skip this house if
- Return
Trueifcnt >= 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
khouses
Why This Works:
- If capability
xallows robbingkhouses, then any capability> xalso 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.
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. Using Wrong Binary Search Template
The Pitfall:
Using while left < right with right = mid instead of the standard template. This variant can be confusing and error-prone.
The Solution:
Use the standard binary search template with while left <= right, first_true_index tracking, and right = mid - 1:
left, right = 0, max(nums)
first_true_index = -1
while left <= right:
mid = (left + right) // 2
if feasible(mid):
first_true_index = mid
right = mid - 1
else:
left = mid + 1
return first_true_index
2. Incorrect Greedy Strategy in Validation Function
The Pitfall: Using a complex greedy strategy instead of the simple approach. Some might try to minimize gaps between robbed houses or use dynamic programming.
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.
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. Off-by-One Error in Binary Search Range
The Pitfall: Not including the upper bound correctly. The capability can equal the maximum value in the array.
The Solution: Always include the upper bound in the search range:
right = max(nums) # Correct: includes the maximum value
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
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
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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!