LeetCode ยท #198

House Robber Solution

Given an array of non-negative integers representing money in each house, return the maximum amount you can rob without robbing two adjacent houses.

01
Section One ยท Problem

Problem Statement

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

Problem #198

๐Ÿ—๏ธ

Pattern

DP โ€” 1D take/skip

You are a professional robber. Each house has a certain amount of money stashed. Adjacent houses have connected security systems โ€” robbing two neighbors triggers an alarm. Return the maximum amount you can rob tonight.

Example
Input: nums = [2,7,9,3,1] Output: 12 // Rob houses 0, 2, 4 โ†’ 2 + 9 + 1 = 12
Constraints
โ€ข 1 โ‰ค nums.length โ‰ค 100 โ€ข 0 โ‰ค nums[i] โ‰ค 400
02
Section Two ยท Approach 1

Recursion โ€” O(2โฟ)

For each house, decide: rob it (skip neighbor, add value) or skip it. Recursively compute both choices and take the max. This explores every possible combination.

Why it works

The recursion correctly tries all valid subsets โ€” no two adjacent houses robbed. The maximum across all valid subsets is the answer.

Why we can do better
Problem: The recursion tree has overlapping subproblems โ€” rob(i) is called multiple times for the same i. Total calls: O(2โฟ). Memoization or bottom-up DP reduces this to O(n) by storing each subproblem result once.
Java โ€” Recursive brute force
class Solution { public int rob(int[] nums) { return helper(nums, nums.length - 1); } private int helper(int[] nums, int i) { if (i < 0) return 0; return Math.max(helper(nums, i-1), // skip house i helper(nums, i-2) + nums[i]); // rob house i } }
Python โ€” Recursive brute force
class Solution: def rob(self, nums: list[int]) -> int: def helper(i: int) -> int: if i < 0: return 0 return max(helper(i-1), helper(i-2) + nums[i]) return helper(len(nums)-1)
MetricValue
TimeO(2โฟ)
SpaceO(n) call stack
03
Section Three ยท Approach 2

Two-Variable DP โ€” O(n) time, O(1) space

At each house, the best choice is max(skip this house = prev best, rob this house = prev-prev best + this house). Since we only look back two steps, two variables suffice.

๐Ÿ’ก Mental model: You're trick-or-treating on a street, but if you knock on a house, the next-door neighbor locks their door. At each house you face a decision: "Is the candy here plus what I collected two doors ago better than what I'd have just walking past?" You only need to remember your best haul as of the last house and the house before that โ€” two numbers in your head. Each house updates these two running totals as you walk down the block.
Algorithm โ€” rolling two variables
  • prev1 = best total including or excluding the previous house.
  • prev2 = best total as of two houses ago.
  • For each house: cur = max(prev1, prev2 + nums[i]). Then shift: prev2 = prev1; prev1 = cur.
  • Return prev1.
๐ŸŽฏ When to recognize this pattern:
  • "Can't pick adjacent elements, maximize/minimize sum." The signal is a take/skip decision at each index with a constraint linking adjacent choices.
  • Variations: LC 213 (circular), LC 740 (Delete and Earn โ€” reduce to House Robber via frequency array), LC 746 (Min Cost Climbing Stairs).
Why two variables, not one:
  • The "rob this house" option needs the best excluding the previous house โ€” that's prev2.
  • If you only tracked one variable, you'd lose the information about what was optimal two steps back after overwriting it.
04
Section Four ยท Trace

Visual Walkthrough

Trace for nums = [2, 7, 9, 3, 1].

House Robber โ€” Two-Variable DP
nums = [2, 7, 9, 3, 1]. Two variables: prev2 (two back), prev1 (one back). Houses: 2 7 9 3 1 Init: prev2=0, prev1=0 i=0 (house=2):cur = max(prev1=0, prev2+2=2) = 2. prev2=0, prev1=2. i=1 (house=7):cur = max(prev1=2, prev2+7=0+7=7) = 7. prev2=2, prev1=7. i=2 (house=9):cur = max(prev1=7, prev2+9=2+9=11) = 11. prev2=7, prev1=11. i=3 (house=3):cur = max(prev1=11, prev2+3=7+3=10) = 11. prev2=11, prev1=11. i=4 (house=1):cur = max(prev1=11, prev2+1=11+1=12) = 12. prev2=11, prev1=12. return 12 โœ“
05
Section Five ยท Implementation

Code โ€” Java & Python

Java โ€” Two-variable DP
class Solution { public int rob(int[] nums) { int prev2 = 0, prev1 = 0; for (int num : nums) { int cur = Math.max(prev1, prev2 + num); // skip or rob prev2 = prev1; prev1 = cur; } return prev1; } }
Python โ€” Two-variable DP
class Solution: def rob(self, nums: list[int]) -> int: prev2 = prev1 = 0 for num in nums: prev2, prev1 = prev1, max(prev1, prev2 + num) return prev1
06
Section Six ยท Analysis

Complexity Analysis

ApproachTimeSpaceTrade-off
Naive recursionO(2โฟ)O(n)Exponential โ€” TLE for n > 30
Memoized recursion / DP arrayO(n)O(n)Correct but uses extra dp[] array
Two-variable DP โ† optimalO(n)O(1)Constant space; minimal code
Why not greedy? Greedy (always rob the richest available house) doesn't work because skipping a house may enable robbing two richer non-adjacent houses. DP considers all valid combinations by building optimal substructure.
07
Section Seven ยท Edge Cases

Edge Cases & Pitfalls

CaseInputWhy It Matters
Single house[5]Rob it. prev1=5 after one iteration.
Two houses[1,2]Rob the richer one. max(1,2)=2.
All zeros[0,0,0]Max is 0. Algorithm handles naturally.
All equal[5,5,5,5]Rob alternating: 5+5=10.
Descending[10,5,3,1]Rob [10,3]=13 vs [5,1]=6. prev2+nums[i] wins early.
Large values100 houses ร— 400 eachMax possible: 50ร—400=20,000 โ€” fits int easily.
โš  Common Mistake: Initializing prev1 = nums[0] and prev2 = 0 then starting the loop at i=1. This works but requires a guard for empty arrays. The cleaner pattern โ€” start both at 0 and loop from the beginning โ€” handles all edge cases uniformly without special-casing nums.length == 1.

โ† Back to DP โ€” 1D problems