You plan to rob houses in a street where each house stores a certain amount of money. The neighborhood has a security system that sets off an alarm when two adjacent houses are robbed. Return the maximum amount of cash that can be stolen without triggering the alarms.
Input: houses = [200, 300, 200, 50]
Output: 400
Explanation: Stealing from the houses at indexes 0 and 2 yields 200 + 200 = 400 dollars.
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Any “pick items for maximum value, but no two chosen items can be next to each other” problem is this one in disguise — scheduling talks that cannot be back-to-back, choosing ad slots that cannot be adjacent, and so on. The greedy “always grab the richest house” fails (grabbing 300 here blocks both 200s), so we need DP.
At the last house you have exactly two choices: skip it (keep the best up to the previous house) or rob it (its cash plus the best up to two houses back, since the neighbor is now off-limits). Take the larger: dp[i] = max(dp[i−1], houses[i] + dp[i−2]).
Ideally, we would want to rob every house, and collect the total sum of all cash contained therein. However, with the alarm system in place, we need to be more strategic about which houses to rob and which to skip.
A simple, greedy approach of always robbing the house with the most money fails because it overlooks the long-term consequences of its choices, and doesn’t always yield the highest total profit. This is visualized in the example below.
Let’s approach this problem from a different angle. Imagine breaking into houses all along a street, and eventually reaching the last house, denoted as i below. How much money has been stolen up to this point?
To answer this question, we need to consider the two choices that can be made at this last house: do we skip it or rob it?
If we skip it, we end our burglary with the total amount stolen up to the house at i - 1:
If we rob it, we couldn't have robbed the previous house at i - 1, so we end the burglary with the money stolen from this final house, plus the total amount stolen up to the house at i - 2, which is two houses back:
The optimal choice is whichever of these two options yields the largest amount of money.
Notice this discussion highlights the existence of subproblems, and an optimal substructure where we need to know the total amount stolen up to the two previous houses, before determining the total amount we can steal up to the current house.
This means we should try using DP to solve this problem. Let’s say dp[i] represents the maximum amount we’re able to steal by the time we reach house i. Based on our previous discussion, we know that:
dp[i]= max(profit if we skip housei, profit if we rob housei) =max(dp[i - 1], houses[i] + dp[i - 2])
Once the DP array is populated using the above formula, we can just return dp[n - 1], which represents the maximum amount that can be stolen once we reach the end of the street. Now, let’s think about what our base cases should be.
Base cases
For starters, let’s consider what to do if there’s just one house. This is the simplest possible subproblem, where the total amount stolen is just the money in that house. So, one of our base cases is dp[0] = houses[0].
Keep in mind that to use our DP formula (dp[i] = max(dp[i - 1], houses[i] + dp[i - 2])), we need to access values at indexes i - 1 and i - 2. This means we must set initial values for index 0 and index 1. With these base cases, we can safely start using the formula from index 2 onward without causing any index out-of-bound errors. So, what’s the most money that can be stolen at i = 1 (i.e., when there are just two adjacent houses)? We can only steal from one of these houses, so dp[1] = max(houses[0], houses[1]).
from typing import List
def neighborhood_burglary(houses: List[int]) -> int:
# Handle the cases when the array is less than the size of 2 to avoid out-of-
# bounds errors when assigning the base case values.
if not houses:
return 0
if len(houses) == 1:
return houses[0]
dp = [0] * len(houses)
# Base case: when there's only one house, rob that house.
dp[0] = houses[0]
# Base case: when there are two houses, rob the one with the most money.
dp[1] = max(houses[0], houses[1])
# Fill in the rest of the DP array.
for i in range(2, len(houses)):
# 'dp[i]' = max(profit if we skip house 'i', profit if we rob house 'i').
dp[i] = max(dp[i - 1], houses[i] + dp[i - 2])
return dp[len(houses) - 1]#include <vector>
#include <algorithm>
using namespace std;
long long neighborhoodBurglary(vector<int>& houses) {
int n = (int)houses.size();
if (n == 0) {
return 0;
}
if (n == 1) {
return houses[0];
}
long long prevPrev = houses[0]; // best up to house 0
long long prev = max(houses[0], houses[1]); // best up to house 1
for (int i = 2; i < n; i++) {
long long cur = max(prev, houses[i] + prevPrev); // skip i vs rob i
prevPrev = prev; // slide forward
prev = cur;
}
return prev;
}// These methods live inside a class, e.g. class Solution { ... }
long neighborhoodBurglary(int[] houses) {
int n = houses.length;
if (n == 0) {
return 0;
}
if (n == 1) {
return houses[0];
}
long prevPrev = houses[0]; // best up to house 0
long prev = Math.max(houses[0], houses[1]); // best up to house 1
for (int i = 2; i < n; i++) {
long cur = Math.max(prev, houses[i] + prevPrev); // skip i vs rob i
prevPrev = prev; // slide forward
prev = cur;
}
return prev;
}Time complexity: The time complexity of neighborhood_burglary is , where denotes the number of houses. This is because each index of the DP array is populated at most once.
Space complexity: The space complexity is since we're maintaining a DP array that has elements.
From the DP array formula dp[i] = max(dp[i - 1], houses[i] + dp[i - 2]), an important observation is that we only need to access the previous two values of the DP array, index i - 1 and index i - 2, to calculate the current value at index i. This means we don’t need to store the entire DP array.
Instead, we can use two variables to keep track of the previous two values:
prev_max_profit: stores the value of dp[i - 1]prev_prev_max_profit: stores the value of dp[i - 2]This optimization reduces the space complexity to since we’re no longer maintaining any auxiliary data structures. The adjusted implementation can be seen below:
from typing import List
def neighborhood_burglary_optimized(houses: List[int]) -> int:
if not houses:
return 0
if len(houses) == 1:
return houses[0]
# Initialize the variables with the base cases.
prev_max_profit = max(houses[0], houses[1])
prev_prev_max_profit = houses[0]
for i in range(2, len(houses)):
curr_max_profit = max(prev_max_profit, houses[i] + prev_prev_max_profit)
# Update the values for the next iteration.
prev_prev_max_profit = prev_max_profit
prev_max_profit = curr_max_profit
return prev_max_profit
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
The two cells are prevPrev (blue) and prev (green). Each house picks max(skip, rob) then slides.
prevPrev = 200 (house 0), prev = max(200, 300) = 300 (house 1).
200 + 200 = 400 vs skip = 300 → 400. Slide.
50 + 300 = 350 vs skip = 400 → 400 → answer 400.
| i | rob = houses[i] + prevPrev | skip = prev | cur = max |
|---|---|---|---|
| 2 | 200 + 200 = 400 | 300 | 400 |
| 3 | 50 + 300 = 350 | 400 | 400 → return 400 |
Best plan robs houses 0 and 2 for 400 — robbing the tempting 300 would have blocked both 200s. ✓
No houses means nothing to steal. The first guard if not houses: return 0 handles it before any indexing.
| houses | guard | action |
|---|---|---|
| [] | empty | return 0 |
| [400] | one house | return 400 |
Result 0 for the empty street (and a lone house returns its own cash). Skipping these guards would crash when seeding houses[1]. ✗
max(prev, houses[i] + prev_prev) is the whole decision. Python integers never overflow.
Use long long for the running totals; many large houses can exceed 32 bits.
Use long and Math.max; mixing int houses with a long accumulator is fine.