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 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
37
1class 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}
43
1class 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};
42
1function 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}
37

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.

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

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

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

Load More