198. House Robber
At a Glance
- Topic: dp-1d
- Pattern: Dynamic Programming (take-or-skip on a line)
- Difficulty: Medium
- Companies: Google, Amazon, Meta, Bloomberg, Microsoft
- Frequency: Very High
- LeetCode: 198
Problem (one-liner)
Given non-negative money per house in a line, you cannot rob two adjacent houses. Maximize the total money robbed. Input: nums array. Output: maximum sum.
Recognition Cues
- “Cannot rob two adjacent”
- “Maximize” on a 1D array with local conflict
- Classic “include/exclude” at each index
Diagram
At-a-glance flow (replace with problem-specific Mermaid as you refine this note). camelCase node IDs; no spaces in IDs.
Loading diagram…
Approaches
- Brute force —
O(2^n)— every subset without adjacent. - Better — top-down DP —
O(n)time /O(n)space. - Optimal — two-state rolling max —
O(n)time /O(1)space. <- pick this in interview.
Optimal Solution
Go
package main
func robTable(nums []int) int {
length := len(nums)
if length == 0 {
return 0
}
if length == 1 {
return nums[0]
}
dp := make([]int, length)
dp[0] = nums[0]
if nums[1] > nums[0] {
dp[1] = nums[1]
} else {
dp[1] = nums[0]
}
for index := 2; index < length; index++ {
skip := dp[index-1]
take := dp[index-2] + nums[index]
if take > skip {
dp[index] = take
} else {
dp[index] = skip
}
}
return dp[length-1]
}
func rob(nums []int) int {
length := len(nums)
if length == 0 {
return 0
}
if length == 1 {
return nums[0]
}
previousPrevious := nums[0]
previous := nums[0]
if nums[1] > nums[0] {
previous = nums[1]
}
for index := 2; index < length; index++ {
take := previousPrevious + nums[index]
skip := previous
next := skip
if take > skip {
next = take
}
previousPrevious = previous
previous = next
}
return previous
}JavaScript
function robTable(nums) {
const length = nums.length;
if (length === 0) return 0;
if (length === 1) return nums[0];
const dp = new Array(length);
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (let index = 2; index < length; index++) {
dp[index] = Math.max(dp[index - 1], dp[index - 2] + nums[index]);
}
return dp[length - 1];
}
function rob(nums) {
const length = nums.length;
if (length === 0) return 0;
if (length === 1) return nums[0];
let previousPrevious = nums[0];
let previous = Math.max(nums[0], nums[1]);
for (let index = 2; index < length; index++) {
const next = Math.max(previous, previousPrevious + nums[index]);
previousPrevious = previous;
previous = next;
}
return previous;
}Walkthrough
Input: nums = [2, 7, 9, 3, 1]
| index | skip (best ending without robbing this?) | take (best if rob this) | best |
|---|---|---|---|
| 0 | 0 | 2 | 2 |
| 1 | 2 | 7 | 7 |
| 2 | 7 | 2+9=11 | 11 |
| 3 | 11 | 7+3=10 | 11 |
| 4 | 11 | 11+1=12 | 12 |
Invariant: rolling (previousPrevious, previous) stores best totals for prefix ending two back and one back.
Edge Cases
- Empty array (often disallowed) →
0 - Single house → that value
- All zeros →
0 - Strictly increasing amounts → pick alternating pattern via DP
Pitfalls
- Greedy “take larger neighbor” fails globally
- Off-by-one when initializing first two entries
- Using full array when
O(1)space suffices
Similar Problems
- 213. House Robber II — circle breaks linearity.
- 746. Min Cost Climbing Stairs — two-step recurrence family.
- 070. Climbing Stairs — simple linear recurrence over a path.
Variants
- Houses arranged in a tree → tree DP with children states (House Robber III).
- Pick exactly
khouses with spacing → constrained knapsack-style DP.
Mind-Map Tags
#dp-1d #take-or-skip #linear-dp #rolling-state #medium
Last updated on
Spotted something unclear or wrong on this page?