Facebook Pixel

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 where nums[i] represents the amount of money in the i-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.

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

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:

  1. Initialize a counter cnt = 0 for houses robbed and j = -2 to track the index of the last robbed house (starting at -2 ensures the first house can always be considered)
  2. Iterate through each house i with value v:
    • Skip this house if v > x (exceeds our capability) or if i == j + 1 (adjacent to last robbed house)
    • Otherwise, rob this house: increment cnt and update j = i
  3. Return True if cnt >= 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) returns True
  • This gives us the minimum capability that allows robbing at least k houses

Why This Works:

  • If capability x allows robbing k 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 Evaluator

Example 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)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More