You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and it will automatically contact the police if two adjacent houses were broken into on the same night. Given an integer array nums
representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police.
Example 1:
Input: nums = [2,3,2]
Output: 3
Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses.
Example 2:
Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.
Example 3:
Input: nums = [1,2,3]
Output: 3
Constraints:
1 <= nums.length <= 100
0 <= nums[i] <= 1000
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
We're trying to find the most money we can steal from houses in a circle, but we can't rob adjacent houses. The brute force way is to consider every possible combination of houses to rob and see which one gives us the most money without robbing neighbors.
Here's how the algorithm would work step-by-step:
def house_robber_two_brute_force(houses):
number_of_houses = len(houses)
if number_of_houses == 0:
return 0
maximum_money_stolen = 0
# Consider two cases: rob the first house or don't
for rob_first_house in [True, False]:
for number_of_houses_to_rob in range(number_of_houses + 1):
for combination_mask in range(2**number_of_houses):
robbed_houses = []
money_stolen = 0
houses_robbed_count = 0
# Check if this combination is valid
for house_index in range(number_of_houses):
if (combination_mask >> house_index) & 1:
robbed_houses.append(house_index)
houses_robbed_count += 1
if houses_robbed_count != number_of_houses_to_rob:
continue
# Check for adjacency and first/last house rule.
valid_combination = True
if rob_first_house:
if 0 not in robbed_houses:
continue
if number_of_houses - 1 in robbed_houses:
continue
for i in range(len(robbed_houses)):
if i > 0:
if robbed_houses[i] - robbed_houses[i - 1] == 1:
valid_combination = False
break
if valid_combination:
for house_index in robbed_houses:
money_stolen += houses[house_index]
maximum_money_stolen = max(maximum_money_stolen, money_stolen)
return maximum_money_stolen
This problem is tricky because the first and last houses are connected, creating a circular arrangement. The clever approach is to break this circle into two separate lines of houses, solving each independently, and then choosing the better outcome.
Here's how the algorithm would work step-by-step:
def house_robber_two(house_values):
number_of_houses = len(house_values)
if number_of_houses == 0:
return 0
if number_of_houses == 1:
return house_values[0]
def rob_linear_houses(house_values_subset):
number_of_houses_subset = len(house_values_subset)
if number_of_houses_subset == 0:
return 0
maximum_money_at_house = [0] * number_of_houses_subset
maximum_money_at_house[0] = house_values_subset[0]
if number_of_houses_subset > 1:
maximum_money_at_house[1] = max(house_values_subset[0], house_values_subset[1])
for i in range(2, number_of_houses_subset):
# Decide whether to rob current or skip
maximum_money_at_house[i] = max(house_values_subset[i] + maximum_money_at_house[i - 2], maximum_money_at_house[i - 1])
return maximum_money_at_house[number_of_houses_subset - 1]
# Exclude the last house
max_money_excluding_last_house = rob_linear_houses(house_values[:-1])
# Exclude the first house
max_money_excluding_first_house = rob_linear_houses(house_values[1:])
# Return the maximum of the two scenarios
return max(max_money_excluding_first_house, max_money_excluding_last_house)
Case | How to Handle |
---|---|
Empty input array | Return 0 if the input array is empty since there are no houses to rob. |
Array with one house | Return the value of the single house since we can only rob it. |
Array with two houses | Return the maximum value of the two houses since we can only rob one. |
All houses have a value of 0 | Return 0 since robbing any house yields 0 profit. |
Houses with negative values (invalid scenario) | Raise an exception or return an error code since house values cannot be negative in the given problem context; alternatively, if permissible, treat negative values as valid amounts and the algorithm should find optimal negative gain. |
Very large house values that can cause integer overflow during summation | Use a data type with a larger range like long to prevent integer overflow when calculating the maximum robbed amount, if language supports it. |
Large input array to ensure algorithm scales efficiently | The dynamic programming solution avoids unnecessary calculations and has a linear time complexity (O(n)), making it efficient for large input arrays. |
Circular dependency between first and last house | The solution handles the circular dependency by considering two separate scenarios: robbing the first house (and not the last) and robbing the last house (and not the first), then taking the maximum of the two scenarios. |