Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P0EasyBuilt on Linear DP~3 min speedrun

House Robber (Non-Adjacent Maximum Sum)

Built on
Linear DPEach item is now take or skip, and taking item i rules out item i - 1, so the recurrence becomes dp[i] = max(dp[i - 1], dp[i - 2] + x).
The problem

House Robber (LC 198)(opens leetcode.com)

Houses stand in a row, each holding a known amount of cash, but any two houses sitting right next to each other share a single alarm, so burgling both in one night sets it off.

Given the stash in each house as nums, find the largest total you can collect in one night without ever choosing two adjacent houses.

Example 1
Input:nums = [1,2,3,1]
Output:4
Choosing house 0 (worth 1) and house 2 (worth 3), which are not adjacent, nets 1 + 3 = 4 in total.

House Robber

dp[i] = max(dp[i-1], dp[i-2] + x) ➔ skip a house or rob it on top of the best from two back ➔ O(1) rolling registers
Spot it when: Items come in a line and each choice only depends on the last one or two choices: take or skip neighbours, steps of 1 or 2, best sum ending here.
House Robber
Python
1def rob(nums: list[int]) -> int:
2 """House Robber: Non-adjacent maximum sum using O(1) rolling space.
3 Recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i])
4 Time: O(N) | Space: O(1)
5 """
6 if not nums:
7 return 0
8 
9 # prev2, prev1: rolling dp[i-2] and dp[i-1], the best totals two and one house back
10 prev2, prev1 = 0, 0
11 for x in nums:
12 # 1. Decide This House:
13 # Skip it and keep prev1, or rob it on top of the best total from two houses back (prev2 + x).
14 curr = max(prev1, prev2 + x)
15 
16 # 2. Roll The Window Forward:
17 # Shift prev2 to the old prev1 before overwriting prev1, since prev1 is still needed for prev2's new value.
18 prev2 = prev1
19 prev1 = curr
20 
21 return prev1
Invariant: After each x, prev1 is the best total for the houses seen so far and prev2 is the best total without the last of them.

Each answer depends only on the last one or two answers: at every house you either skip it (prev1) or rob it on top of the best from two houses back (prev2 + x). So the whole dp table collapses into two rolling variables that shift forward once per item. Shift in the right order, curr first, then prev2 = prev1, then prev1 = curr, or you overwrite a value you still need.

See it run

House Robber, step by step

prev2 0 · prev1 0

prev2 0 · prev1 0

prev2 0 · prev1 0

prev2 0 · prev1 2

prev2 0 · prev1 2

prev2 2 · prev1 7

prev2 2 · prev1 7

prev2 7 · prev1 11

prev2 7 · prev1 11

prev2 11 · prev1 11

prev2 11 · prev1 11

prev2 11 · prev1 12 · answer 12

Each column is one house. Each cell in the dp row is the best total so far.

  • x the house being read
  • prev1 the best total up to the previous house (skip this house)
  • prev2 the best total up to two houses back (rob this house)
  • curr the new best total after this house

Start: prev2 = 0 and prev1 = 0. Zero houses give a total of 0.

House 0 has x = 2. Skip it and keep prev1 = 0, or rob it for prev2 + x = 0 + 2 = 2.

max(0, 2) = 2, so robbing house 0 is better. curr = 2.

House 1 has x = 7. Skip it and keep prev1 = 2, or rob it for prev2 + x = 0 + 7 = 7.

max(2, 7) = 7, so robbing house 1 is better. curr = 7.

House 2 has x = 9. Skip it and keep prev1 = 7, or rob it for prev2 + x = 2 + 9 = 11.

max(7, 11) = 11, so robbing house 2 is better. curr = 11.

House 3 has x = 3. Skip it and keep prev1 = 11, or rob it for prev2 + x = 7 + 3 = 10.

max(11, 10) = 11, so skipping house 3 is at least as good. curr = 11.

House 4 has x = 1. Skip it and keep prev1 = 11, or rob it for prev2 + x = 11 + 1 = 12.

max(11, 12) = 12, so robbing house 4 is better. curr = 12.

Done: the shift prev2 = prev1 and prev1 = curr ran after every house. The answer is prev1 = 12.

nums
02
17
29
33
41
dp
1 / 12
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Setting prev1 = curr before prev2 = prev1

    Mistake
    prev1 = curr
    prev2 = prev1
    Fix
    prev2 = prev1
    prev1 = curr

    What happens: prev1 holds dp[i-1] and prev2 holds dp[i-2]. The next dp[i-2] is the old prev1. If prev1 = curr runs first, that old value is lost. Then prev2 = prev1 copies curr, so both variables hold the same value.

    Example: nums = [2, 7, 9, 3, 1]
    Mistake: returns 22Fix: returns 12
  2. 2

    Gotcha 2: Mixing 'best ending at house i' with 'best so far'

    Mistake
    curr = prev2 + x
    Fix
    curr = max(prev1, prev2 + x)

    What happens: Decide what dp[i] means before you write the formula. Here dp[i] is the best total using the first i houses. curr = prev2 + x is a different meaning, the best total that robs house i. The loop still returns prev1 as the best so far, so the answer is too small.

    Example: nums = [2, 1, 1, 2]
    Mistake: returns 3Fix: returns 4
  3. 3

    Gotcha 3: Running the straight version on a circle of houses

    Mistake
    return rob(nums)
    Fix
    return max(rob(nums[1:]), rob(nums[:-1]))

    What happens: In House Robber II (LC 213), the first and last houses are neighbours. rob(nums) can rob both of them. Run the straight version twice instead, once without the first house and once without the last. A single house needs its own case, because both slices are then empty.

    Example: House Robber II (LC 213): nums = [2, 3, 2]
    Mistake: returns 4Fix: returns 3
Not this one if…
  • One locally best move at each index can never hurt a later one, as in Jump Game (LC 55): there are no earlier answers to weigh against each other. See Greedy
  • Each day you are in one of several modes (holding a stock or not) with rules for moving between them, as in Best Time to Buy and Sell Stock with Transaction Fee (LC 714): one number per index can't say which mode you are in. See State Machine DP

Deep Dive

Why dp[i] = max(dp[i - 1], dp[i - 2] + x) is correct

Here dp[i] is the best total you can take from the first i houses. No two chosen houses may be next to each other.

Look at the last house, with value x. There are only two cases. In the first case, you skip it. Then the best total is the best total for the first i - 1 houses, which is dp[i - 1].

In the second case, you rob it. Then the previous house must be skipped, so the rest comes from the first i - 2 houses. The best total is dp[i - 2] + x.

The answer is the larger of the two. Every valid choice falls into one of these cases, so no choice is missed.

Example: for nums = [2, 7, 9, 3, 1], the answer is 12. It is the sum of houses 2, 9 and 1.

What this card adds to Linear DP

Linear DP counts paths and adds two answers. This card makes a choice with max. The loop, the two rolling variables and the shift order stay the same.

The starting values are prev2, prev1 = 0, 0. They mean that zero houses give a total of 0. The loop then starts at the first house, and not at index 2.

The word "best so far" is exact here. prev1 is the best total for all houses seen, not the best total that ends at the last house. This matters, as the next section shows.

Trace and the cost

Trace for nums = [2, 7, 9, 3, 1]. Each row shows x, then prev1 (skip), then prev2 + x (rob), then curr:

  • x = 2: skip 0, rob 2, curr = 2.
  • x = 7: skip 2, rob 7, curr = 7.
  • x = 9: skip 7, rob 11, curr = 11.
  • x = 3: skip 11, rob 10, curr = 11.
  • x = 1: skip 11, rob 12, curr = 12.

There are N states and each does one max. The time is O(N) and the space is O(1).

The same code works for a table of N values with constraints on neighbours only. It does not work when a choice also blocks houses far away.

Why simple rules fail

Two simple ideas look right and are wrong. The first is to take the largest house, remove its neighbours, and repeat. For [5, 6, 5], this takes 6 and returns 6. The answer is 10, from the two houses of value 5.

The second is to compare the sum of the even positions with the sum of the odd positions. For [2, 1, 1, 2], both sums are 3. The answer is 4, from the first and the last house.

A locally best choice can block a better pair. The recurrence works because it compares both complete choices at each house.

60-Second Pitch

"At each house I have two choices. I skip it and keep the best total so far, or I rob it and add its value to the best total from two houses back. I take the larger one. That gives dp[i] = max(dp[i - 1], dp[i - 2] + x). It reads only two earlier answers, so I use prev1 and prev2. The time is O(N) and the space is O(1). For a circle of houses, the first and last houses touch. I run the same function twice, once without the first house and once without the last, and take the maximum."

Common Questions

Why is the greedy rule "take the largest house" wrong?

The largest house can block two good neighbours. For [5, 6, 5], the greedy rule takes 6 and returns 6. The correct answer is 10, from the two houses of value 5. The recurrence compares both complete choices at every house, so it does not make this mistake.

How do you solve House Robber II (LC 213), where the houses form a circle?

The first and last houses touch, so you cannot rob both. Run rob on nums[1:], which skips the first house, and on nums[:-1], which skips the last house. Return the larger result. For [2, 3, 2], the answer is 3. If there is one house, return its value, because both slices are empty.

What is the time and space complexity of rob?

O(N) time, because each house does one max. O(1) space, because only prev1, prev2 and curr exist.

How does Delete and Earn (LC 740) use this card?

Add up the points for each value. Taking value v removes v - 1 and v + 1, so neighbouring values cannot both be taken. Lay out the sums by value, from the smallest to the largest value, with zero for missing values. Then run rob on that list.

How do you return the chosen houses and not only the total?

Keep a full dp table and walk back from the end. At house i, if dp[i] == dp[i - 1], house i was skipped. Otherwise, house i was robbed, and you continue from i - 2. This needs O(N) space.

SWE Reasoning

Spot it

  • Items are in a line, and you choose a subset to maximise a total.
  • Two chosen items may not be neighbours.
  • A greedy rule, such as "take the biggest", fails on a small example like [5, 6, 5].
  • The answer for the first i items depends only on the answers for the first i - 1 and i - 2 items.
  • Values are not negative, so skipping an item is always allowed and taking it never hurts.

The invariant, step by step

  1. Before the loop, prev2 and prev1 are both 0. No house has been seen, so the best total is 0.
  2. For the next house with value x, prev1 is the best total for all houses before it. prev2 is the best total without the last of them.
  3. Skipping the house keeps the total prev1. Robbing it gives prev2 + x, because its left neighbour is skipped.
  4. curr = max(prev1, prev2 + x) is the best total for the houses up to and including this one.
  5. prev2 = prev1 runs first, then prev1 = curr. The invariant holds for the next house.
  6. After the last house, prev1 is the best total for all houses.

Traps

  • Wrong shift order. If prev1 = curr runs first, both variables hold curr. For [2, 7, 9, 3, 1], the result is 22 instead of 12.
  • Best ending here, not best so far. curr = prev2 + x forgets the skip case. For [2, 1, 1, 2], the result is 3 instead of 4.
  • Circle of houses. In House Robber II (LC 213), the first and last houses are neighbours. rob(nums) can take both. Run rob on nums[1:] and on nums[:-1], and return the larger result. Handle one house on its own.
  • Empty list. The blueprint returns 0 for an empty list. Without that check, a version that reads nums[0] raises IndexError.
  • Hidden extra rule. If the problem also blocks values far away, the two-back rule is not enough. Change the state before you reuse this code.

Practice

Canonical LeetCode:House Robber (LC 198)House Robber II (LC 213)Delete and Earn (LC 740)
Theory Context•Dynamic Programming

Builds on Linear DP, which you haven’t finished yet.

P0EasyBuilt on Linear DP~3 min speedrun

House Robber (Non-Adjacent Maximum Sum)

Built on
Linear DPEach item is now take or skip, and taking item i rules out item i - 1, so the recurrence becomes dp[i] = max(dp[i - 1], dp[i - 2] + x).
The problem

House Robber (LC 198)(opens leetcode.com)

Houses stand in a row, each holding a known amount of cash, but any two houses sitting right next to each other share a single alarm, so burgling both in one night sets it off.

Given the stash in each house as nums, find the largest total you can collect in one night without ever choosing two adjacent houses.

Example 1
Input:nums = [1,2,3,1]
Output:4
Choosing house 0 (worth 1) and house 2 (worth 3), which are not adjacent, nets 1 + 3 = 4 in total.

House Robber

dp[i] = max(dp[i-1], dp[i-2] + x) ➔ skip a house or rob it on top of the best from two back ➔ O(1) rolling registers
Spot it when: Items come in a line and each choice only depends on the last one or two choices: take or skip neighbours, steps of 1 or 2, best sum ending here.
House Robber
Python
1def rob(nums: list[int]) -> int:
2 """House Robber: Non-adjacent maximum sum using O(1) rolling space.
3 Recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i])
4 Time: O(N) | Space: O(1)
5 """
6 if not nums:
7 return 0
8 
9 # prev2, prev1: rolling dp[i-2] and dp[i-1], the best totals two and one house back
10 prev2, prev1 = 0, 0
11 for x in nums:
12 # 1. Decide This House:
13 # Skip it and keep prev1, or rob it on top of the best total from two houses back (prev2 + x).
14 curr = max(prev1, prev2 + x)
15 
16 # 2. Roll The Window Forward:
17 # Shift prev2 to the old prev1 before overwriting prev1, since prev1 is still needed for prev2's new value.
18 prev2 = prev1
19 prev1 = curr
20 
21 return prev1
Invariant: After each x, prev1 is the best total for the houses seen so far and prev2 is the best total without the last of them.

Each answer depends only on the last one or two answers: at every house you either skip it (prev1) or rob it on top of the best from two houses back (prev2 + x). So the whole dp table collapses into two rolling variables that shift forward once per item. Shift in the right order, curr first, then prev2 = prev1, then prev1 = curr, or you overwrite a value you still need.

See it run

House Robber, step by step

prev2 0 · prev1 0

prev2 0 · prev1 0

prev2 0 · prev1 0

prev2 0 · prev1 2

prev2 0 · prev1 2

prev2 2 · prev1 7

prev2 2 · prev1 7

prev2 7 · prev1 11

prev2 7 · prev1 11

prev2 11 · prev1 11

prev2 11 · prev1 11

prev2 11 · prev1 12 · answer 12

Each column is one house. Each cell in the dp row is the best total so far.

  • x the house being read
  • prev1 the best total up to the previous house (skip this house)
  • prev2 the best total up to two houses back (rob this house)
  • curr the new best total after this house

Start: prev2 = 0 and prev1 = 0. Zero houses give a total of 0.

House 0 has x = 2. Skip it and keep prev1 = 0, or rob it for prev2 + x = 0 + 2 = 2.

max(0, 2) = 2, so robbing house 0 is better. curr = 2.

House 1 has x = 7. Skip it and keep prev1 = 2, or rob it for prev2 + x = 0 + 7 = 7.

max(2, 7) = 7, so robbing house 1 is better. curr = 7.

House 2 has x = 9. Skip it and keep prev1 = 7, or rob it for prev2 + x = 2 + 9 = 11.

max(7, 11) = 11, so robbing house 2 is better. curr = 11.

House 3 has x = 3. Skip it and keep prev1 = 11, or rob it for prev2 + x = 7 + 3 = 10.

max(11, 10) = 11, so skipping house 3 is at least as good. curr = 11.

House 4 has x = 1. Skip it and keep prev1 = 11, or rob it for prev2 + x = 11 + 1 = 12.

max(11, 12) = 12, so robbing house 4 is better. curr = 12.

Done: the shift prev2 = prev1 and prev1 = curr ran after every house. The answer is prev1 = 12.

nums
02
17
29
33
41
dp
1 / 12
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Setting prev1 = curr before prev2 = prev1

    Mistake
    prev1 = curr
    prev2 = prev1
    Fix
    prev2 = prev1
    prev1 = curr

    What happens: prev1 holds dp[i-1] and prev2 holds dp[i-2]. The next dp[i-2] is the old prev1. If prev1 = curr runs first, that old value is lost. Then prev2 = prev1 copies curr, so both variables hold the same value.

    Example: nums = [2, 7, 9, 3, 1]
    Mistake: returns 22Fix: returns 12
  2. 2

    Gotcha 2: Mixing 'best ending at house i' with 'best so far'

    Mistake
    curr = prev2 + x
    Fix
    curr = max(prev1, prev2 + x)

    What happens: Decide what dp[i] means before you write the formula. Here dp[i] is the best total using the first i houses. curr = prev2 + x is a different meaning, the best total that robs house i. The loop still returns prev1 as the best so far, so the answer is too small.

    Example: nums = [2, 1, 1, 2]
    Mistake: returns 3Fix: returns 4
  3. 3

    Gotcha 3: Running the straight version on a circle of houses

    Mistake
    return rob(nums)
    Fix
    return max(rob(nums[1:]), rob(nums[:-1]))

    What happens: In House Robber II (LC 213), the first and last houses are neighbours. rob(nums) can rob both of them. Run the straight version twice instead, once without the first house and once without the last. A single house needs its own case, because both slices are then empty.

    Example: House Robber II (LC 213): nums = [2, 3, 2]
    Mistake: returns 4Fix: returns 3
Not this one if…
  • One locally best move at each index can never hurt a later one, as in Jump Game (LC 55): there are no earlier answers to weigh against each other. See Greedy
  • Each day you are in one of several modes (holding a stock or not) with rules for moving between them, as in Best Time to Buy and Sell Stock with Transaction Fee (LC 714): one number per index can't say which mode you are in. See State Machine DP

Deep Dive

Why dp[i] = max(dp[i - 1], dp[i - 2] + x) is correct

Here dp[i] is the best total you can take from the first i houses. No two chosen houses may be next to each other.

Look at the last house, with value x. There are only two cases. In the first case, you skip it. Then the best total is the best total for the first i - 1 houses, which is dp[i - 1].

In the second case, you rob it. Then the previous house must be skipped, so the rest comes from the first i - 2 houses. The best total is dp[i - 2] + x.

The answer is the larger of the two. Every valid choice falls into one of these cases, so no choice is missed.

Example: for nums = [2, 7, 9, 3, 1], the answer is 12. It is the sum of houses 2, 9 and 1.

What this card adds to Linear DP

Linear DP counts paths and adds two answers. This card makes a choice with max. The loop, the two rolling variables and the shift order stay the same.

The starting values are prev2, prev1 = 0, 0. They mean that zero houses give a total of 0. The loop then starts at the first house, and not at index 2.

The word "best so far" is exact here. prev1 is the best total for all houses seen, not the best total that ends at the last house. This matters, as the next section shows.

Trace and the cost

Trace for nums = [2, 7, 9, 3, 1]. Each row shows x, then prev1 (skip), then prev2 + x (rob), then curr:

  • x = 2: skip 0, rob 2, curr = 2.
  • x = 7: skip 2, rob 7, curr = 7.
  • x = 9: skip 7, rob 11, curr = 11.
  • x = 3: skip 11, rob 10, curr = 11.
  • x = 1: skip 11, rob 12, curr = 12.

There are N states and each does one max. The time is O(N) and the space is O(1).

The same code works for a table of N values with constraints on neighbours only. It does not work when a choice also blocks houses far away.

Why simple rules fail

Two simple ideas look right and are wrong. The first is to take the largest house, remove its neighbours, and repeat. For [5, 6, 5], this takes 6 and returns 6. The answer is 10, from the two houses of value 5.

The second is to compare the sum of the even positions with the sum of the odd positions. For [2, 1, 1, 2], both sums are 3. The answer is 4, from the first and the last house.

A locally best choice can block a better pair. The recurrence works because it compares both complete choices at each house.

60-Second Pitch

"At each house I have two choices. I skip it and keep the best total so far, or I rob it and add its value to the best total from two houses back. I take the larger one. That gives dp[i] = max(dp[i - 1], dp[i - 2] + x). It reads only two earlier answers, so I use prev1 and prev2. The time is O(N) and the space is O(1). For a circle of houses, the first and last houses touch. I run the same function twice, once without the first house and once without the last, and take the maximum."

Common Questions

Why is the greedy rule "take the largest house" wrong?

The largest house can block two good neighbours. For [5, 6, 5], the greedy rule takes 6 and returns 6. The correct answer is 10, from the two houses of value 5. The recurrence compares both complete choices at every house, so it does not make this mistake.

How do you solve House Robber II (LC 213), where the houses form a circle?

The first and last houses touch, so you cannot rob both. Run rob on nums[1:], which skips the first house, and on nums[:-1], which skips the last house. Return the larger result. For [2, 3, 2], the answer is 3. If there is one house, return its value, because both slices are empty.

What is the time and space complexity of rob?

O(N) time, because each house does one max. O(1) space, because only prev1, prev2 and curr exist.

How does Delete and Earn (LC 740) use this card?

Add up the points for each value. Taking value v removes v - 1 and v + 1, so neighbouring values cannot both be taken. Lay out the sums by value, from the smallest to the largest value, with zero for missing values. Then run rob on that list.

How do you return the chosen houses and not only the total?

Keep a full dp table and walk back from the end. At house i, if dp[i] == dp[i - 1], house i was skipped. Otherwise, house i was robbed, and you continue from i - 2. This needs O(N) space.

SWE Reasoning

Spot it

  • Items are in a line, and you choose a subset to maximise a total.
  • Two chosen items may not be neighbours.
  • A greedy rule, such as "take the biggest", fails on a small example like [5, 6, 5].
  • The answer for the first i items depends only on the answers for the first i - 1 and i - 2 items.
  • Values are not negative, so skipping an item is always allowed and taking it never hurts.

The invariant, step by step

  1. Before the loop, prev2 and prev1 are both 0. No house has been seen, so the best total is 0.
  2. For the next house with value x, prev1 is the best total for all houses before it. prev2 is the best total without the last of them.
  3. Skipping the house keeps the total prev1. Robbing it gives prev2 + x, because its left neighbour is skipped.
  4. curr = max(prev1, prev2 + x) is the best total for the houses up to and including this one.
  5. prev2 = prev1 runs first, then prev1 = curr. The invariant holds for the next house.
  6. After the last house, prev1 is the best total for all houses.

Traps

  • Wrong shift order. If prev1 = curr runs first, both variables hold curr. For [2, 7, 9, 3, 1], the result is 22 instead of 12.
  • Best ending here, not best so far. curr = prev2 + x forgets the skip case. For [2, 1, 1, 2], the result is 3 instead of 4.
  • Circle of houses. In House Robber II (LC 213), the first and last houses are neighbours. rob(nums) can take both. Run rob on nums[1:] and on nums[:-1], and return the larger result. Handle one house on its own.
  • Empty list. The blueprint returns 0 for an empty list. Without that check, a version that reads nums[0] raises IndexError.
  • Hidden extra rule. If the problem also blocks values far away, the two-back rule is not enough. Change the state before you reuse this code.

Practice

Canonical LeetCode:House Robber (LC 198)House Robber II (LC 213)Delete and Earn (LC 740)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•