House Robber IV
MedPrompt
There are several consecutive houses along a street, each of which has some money inside. There is also a robber, who wants to steal money from the homes, but he refuses to steal from adjacent homes.
The capability of the robber is the maximum amount of money he steals from one house of all the houses he robbed.
You are given an integer array nums representing how much money is stashed in each house. More formally, the ith house from the left has nums[i] dollars.
You are also given an integer k, representing the minimum number of houses the robber will steal from. It is always possible to steal at least k houses.
Return the minimum capability of the robber out of all the possible ways to steal at least k houses.
Example 1:
Input: nums = [2,3,5,9], k = 2
Output: 5
Explanation:
There are three ways to rob at least 2 houses:
- Rob the houses at indices 0 and 2. Capability is max(nums[0], nums[2]) = 5.
- Rob the houses at indices 0 and 3. Capability is max(nums[0], nums[3]) = 9.
- Rob the houses at indices 1 and 3. Capability is max(nums[1], nums[3]) = 9.
Therefore, we return min(5, 9, 9) = 5.Example 2:
Input: nums = [2,7,9,3,1], k = 2
Output: 2
Explanation: There are 7 ways to rob the houses. The way which leads to minimum capability is to rob the house at index 0 and 4. Return max(nums[0], nums[4]) = 2.
Constraints:
1 <= nums.length <= 1051 <= nums[i] <= 1091 <= k <= (nums.length + 1)/2
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses dynamic programming to build the solution from smaller subproblems. We define a state dp[i][j] as the minimum capability required to rob exactly j non-adjacent houses from the first i available houses. By computing the values for this state iteratively, we can determine the minimum capability for robbing k houses from the entire array.
Algorithm
- Initialize a 2D array
dpof size(n+1) x (k+1)with a very large value (infinity) to store the minimum capabilities. - Set the base cases:
dp[i][0] = 0for alli, as robbing zero houses requires zero capability. - Iterate
ifrom 1 ton(representing the firstihouses). - Inside, iterate
jfrom 1 tok(representing the number of houses to rob). - For each
dp[i][j], calculate the value based on two choices for housei-1:- Don't rob house
i-1: The capability is inherited from the solution for the firsti-1houses, i.e.,dp[i-1][j]. - Rob house
i-1: This is only possible if we didn't rob housei-2. The capability will be the maximum of the current house's value (nums[i-1]) and the capability required to robj-1houses from the firsti-2houses (dp[i-2][j-1]).
- Don't rob house
- The recurrence relation is
dp[i][j] = min(option1, option2). - The final answer is
dp[n][k].
Walkthrough
We can formulate a solution using a 2D DP table, dp[i][j], where i corresponds to the number of houses considered from the start of the array, and j is the number of houses we need to rob.
The state dp[i][j] will store the minimum capability to rob j houses from the subarray nums[0...i-1].
The recurrence relation is derived by considering two choices for the i-th house (at index i-1):
- Don't rob house
i: If we don't rob the current house, the problem reduces to robbingjhouses from the firsti-1houses. The minimum capability required for this is simplydp[i-1][j]. - Rob house
i: If we rob the current house, we cannot have robbed the previous house (i-1). This means we must have robbedj-1houses from the firsti-2houses. The capability for this new set of robberies is determined by the largest value among the robbed houses, which ismax(nums[i-1], dp[i-2][j-1]).
The value of dp[i][j] is the minimum of these two options. The final answer is dp[n][k]. While correct, this approach is not efficient enough for the problem's constraints.
// This is a conceptual illustration. It will result in Time Limit Exceeded.class Solution { public int minCapability(int[] nums, int k) { int n = nums.length; if (k == 0) return 0; // dp[i][j]: min capability to rob j houses from first i houses long[][] dp = new long[n + 1][k + 1]; long INF = 2_000_000_000L; // Larger than max possible capability for (int j = 1; j <= k; j++) { dp[0][j] = INF; } for (int i = 0; i <= n; i++) { dp[i][0] = 0; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= k; j++) { // Option 1: Don't rob house i-1 long option1 = dp[i - 1][j]; // Option 2: Rob house i-1 long robCurrentHouseCap = nums[i - 1]; long prevCap = (i >= 2) ? dp[i - 2][j - 1] : (j == 1 ? 0 : INF); long option2 = Math.max(robCurrentHouseCap, prevCap); dp[i][j] = Math.min(option1, option2); } } return (int)dp[n][k]; }}Complexity
Time
O(n * k), where `n` is the number of houses and `k` is the number of houses to rob. We fill a DP table of size `(n+1) x (k+1)`.
Space
O(n * k). This can be optimized to O(k) because the calculation for row `i` only depends on rows `i-1` and `i-2`.
Trade-offs
Pros
Provides a structured way to solve the problem by breaking it down into subproblems.
Guaranteed to find the correct answer for smaller inputs.
Cons
The time complexity of O(n * k) is too slow for the given constraints, where
ncan be up to 10^5 andkcan be up ton/2. This will result in a 'Time Limit Exceeded' error on most platforms.
This optimal approach reframes the problem from finding the minimum capability to checking if a given capability is feasible. This allows us to use binary search on the answer. The key observation is that the problem has a monotonic property: if we can rob k houses with a capability of C, we can certainly do so with any capability C' > C. This allows us to efficiently search for the smallest possible C.
Algorithm
- Define a search range for the capability. The lower bound
leftcan be 1, and the upper boundrightcan be the maximum possible value of money in a house (10^9). - Start a binary search loop while
left <= right. - In each iteration, calculate the middle value
mid = left + (right - left) / 2. Thismidis our candidate for the minimum capability. - Create a helper function
canRob(capability)that checks if it's possible to rob at leastkhouses with the givencapability. - The
canRobfunction works greedily: Iterate through the houses. Ifnums[i] <= capability, we rob this house, increment a counter, and skip the next house (i += 2). Otherwise, we cannot rob it and move to the next house (i++). The function returnstrueif the counter is at leastk. - If
canRob(mid)istrue, it meansmidis a valid capability. We might find an even smaller one, so we recordmidas a potential answer and search in the lower half:ans = mid,right = mid - 1. - If
canRob(mid)isfalse, the capabilitymidis too restrictive. We need a larger capability, so we search in the upper half:left = mid + 1. - After the loop terminates,
answill hold the minimum capability found.
Walkthrough
The problem asks for the minimum capability that satisfies a certain condition. This structure is a perfect fit for Binary Search on the Answer.
The range of possible answers for the capability is between the minimum and maximum money values in the nums array. We can binary search within this range (e.g., from 1 to 10^9 based on constraints).
For each mid value (a potential capability) in our binary search, we must verify if it's possible to rob at least k houses where each house has nums[i] <= mid. This verification can be done with a simple and efficient greedy helper function, canRob(capability).
The canRob(capability) function:
This function greedily determines the maximum number of non-adjacent houses we can rob under the given capability constraint. We iterate through the houses from left to right:
- If
nums[i] <= capability, it's a house we can potentially rob. To maximize the number of houses, we should rob it. We increment our count of robbed houses and then skip the next house (i+1) because of the non-adjacent rule. - If
nums[i] > capability, we cannot rob this house, so we simply move on. If, after checking all houses, the total count of robbed houses is at leastk, the function returnstrue.
By combining binary search with this greedy check, we can find the minimum capability in an efficient manner.
class Solution { public int minCapability(int[] nums, int k) { int left = 1, right = 1_000_000_000; int ans = right; while (left <= right) { int mid = left + (right - left) / 2; if (canRob(nums, k, mid)) { // mid is a possible capability, try for a smaller one ans = mid; right = mid - 1; } else { // mid is too small, need a larger capability left = mid + 1; } } return ans; } // Helper function to check if we can rob at least k houses with a given capability private boolean canRob(int[] nums, int k, int capability) { int housesRobbed = 0; int i = 0; while (i < nums.length) { if (nums[i] <= capability) { housesRobbed++; i += 2; // Rob this house and skip the next one } else { i++; // Cannot rob this house, move to the next } } return housesRobbed >= k; }}Complexity
Time
O(N * log(M)), where N is the number of houses and M is the range of possible money values (the search space for capability, e.g., 10^9). The `canRob` helper function takes O(N), and it's called O(log(M)) times by the binary search.
Space
O(1), as the algorithm only requires a few variables for the binary search and the greedy check, independent of the input size.
Trade-offs
Pros
Highly efficient with a time complexity of O(N log M), which easily passes the given constraints.
The space complexity is optimal at O(1).
The greedy check function is simple to reason about and implement.
Cons
The core idea of applying binary search to the answer space might not be immediately obvious.
Solutions
Solution
class Solution {public int minCapability(int[] nums, int k) { int left = 0, right = (int)1 e9; while (left < right) { int mid = (left + right) >> 1; if (f(nums, mid) >= k) { right = mid; } else { left = mid + 1; } } return left; }private int f(int[] nums, int x) { int cnt = 0, j = -2; for (int i = 0; i < nums.length; ++i) { if (nums[i] > x || i == j + 1) { continue; } ++cnt; j = i; } return cnt; }}class Solution {public: int minCapability(vector<int> &nums, int k) { auto f = [&](int x) { int cnt = 0, j = -2; for (int i = 0; i < nums.size(); ++i) { if (nums[i] > x || i == j + 1) { continue; } ++cnt; j = i; } return cnt >= k; }; int left = 0, right = *max_element(nums.begin(), nums.end()); while (left < right) { int mid = (left + right) >> 1; if (f(mid)) { right = mid; } else { left = mid + 1; } } return left; }};class Solution: def minCapability(self, nums: List[int], k: int) -> int: def f(x): cnt, j = 0, - 2 for i, v in enumerate(nums): if v > x or i == j + 1: continue cnt += 1 j = i return cnt >= k return bisect_left(range(max(nums) + 1), True, key=f)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.