DEV Community

Timevolt
Timevolt

Posted on

The DP Awakens: From Zero to Hero (Star Wars)

The Quest Begins (The "Why")

I still remember the first time I stared at a LeetCode problem titled House Robber and felt my brain short‑circuit. The prompt was simple: Given an array of non‑negative integers representing the amount of money in each house, return the maximum amount you can rob without alerting the police (i.e., you can’t rob two adjacent houses).

My first instinct? Write a recursive function that tried every combination. I imagined myself as a bounty hunter scanning a galaxy of possibilities, each branch exploding into two more—the classic exponential blow‑up. After a few minutes of watching the call stack grow like a Death Star trench, I realized I was stuck in a loop that would never finish for anything beyond size 20.

That moment sparked a question: Is there a way to reuse work I’ve already done? The answer, as many of you know, lies in dynamic programming—but not the intimidating, textbook‑y version. I wanted to understand the why behind the recurrence, not just memorize a formula. So I embarked on a mini‑quest to uncover the pattern that turns an impossible exponential search into a clean, linear sweep.

The Revelation (The Insight)

The House Robber problem hides a beautiful property: optimal substructure. If I know the best loot I can collect up to house i‑1 and up to house i‑2, then the best loot up to house i is simply the better of:

  1. Skip house i → take the best loot up to i‑1.
  2. Rob house i → add its value to the best loot up to i‑2 (since I can’t touch i‑1).

That’s it. No need to consider any farther back houses because any solution that ends at i must have made a decision about i‑1 (either rob it or skip it), and the part before that decision is already optimally solved.

The overlapping subproblems appear because the same sub‑array (0 … i‑2) is needed repeatedly when we evaluate different later houses. By storing just the two previous results, we avoid recomputing them—turning the naïve recursion into an O(n) walk.

Think of it like Neo in The Matrix dodging bullets: instead of trying to predict every possible trajectory, he realizes the pattern—move left, then right, then left again—based on only the immediate previous moves. Our DP does the same: we keep only the last two “states” and let them guide the next step.

Mathematically, if dp[i] is the max money robbed from houses 0 … i:

dp[i] = max(dp[i-1], dp[i-2] + nums[i])
Enter fullscreen mode Exit fullscreen mode

Base cases:

  • dp[0] = nums[0] (only one house)
  • dp[1] = max(nums[0], nums[1]) (pick the richer of the first two)

Notice we never need the whole array; we only ever look back two steps. That lets us collapse the DP table into two variables, giving O(1) space.

Wielding the Power (Code & Examples)

The Struggle: Exponential Recursion (what NOT to do)

function robRec(nums, i) {
    if (i < 0) return 0;
    // rob this house or skip it
    return Math.max(
        robRec(nums, i - 1),                // skip i
        robRec(nums, i - 2) + nums[i]       // take i
    );
}
function rob(nums) {
    return robRec(nums, nums.length - 1);
}
Enter fullscreen mode Exit fullscreen mode

Running this on an array of length 30 already feels like waiting for a podrace to finish—painfully slow.

The Victory: Linear DP with O(1) Space

function rob(nums) {
    if (!nums.length) return 0;
    if (nums.length === 1) return nums[0];

    let prev2 = nums[0];               // dp[i-2]
    let prev1 = Math.max(nums[0], nums[1]); // dp[i-1]

    for (let i = 2; i < nums.length; i++) {
        const curr = Math.max(prev1, prev2 + nums[i]);
        prev2 = prev1;                 // shift window
        prev1 = curr;
    }
    return prev1;
}
Enter fullscreen mode Exit fullscreen mode

Why this works: Each iteration computes dp[i] using only the two previously stored values. The loop advances exactly like a conveyor belt—no backtracking, no repeated work.

Common traps to avoid:

  1. Forgetting the base case when nums.length === 0. Returning 0 is correct; otherwise you’ll try to access nums[0] and crash.
  2. Using an array for dp when you don’t need it. It’s tempting to allocate dp = new Array(n), but that adds O(n) space for no gain. Two variables are enough.

A Second Interview Twist: House Robber II (circular street)

Now the houses form a circle—you can’t rob the first and last house together. The trick? Run the same linear DP twice:

  1. Exclude the first house, solve on nums[1 … n-1].
  2. Exclude the last house, solve on nums[0 … n-2].

Take the max of the two results.

function rob(nums) {
    if (nums.length === 1) return nums[0];
    return Math.max(
        robLinear(nums.slice(1)),   // skip first
        robLinear(nums.slice(0, -1)) // skip last
    );
}

function robLinear(arr) {
    let prev2 = 0, prev1 = 0;
    for (const val of arr) {
        const curr = Math.max(prev1, prev2 + val);
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}
Enter fullscreen mode Exit fullscreen mode

Still O(n) time, O(1) space—just applied to two slices.

Why This New Power Matters

Mastering this pattern does more than let you ace a LeetCode medium. It trains you to spot optimal substructure and overlapping subproblems everywhere—whether you’re scheduling tasks, allocating resources, or even designing AI that plans moves in a game.

The House Robber DP is the gateway drug to harder variants: Delete and Earn, Maximum Sum of Non‑Adjacent Elements, Stock Buy‑Sell with Cooldown, and beyond. Once you internalize the “keep only the last two states” trick, you start seeing DP as a lightweight tool rather than a heavyweight theorem.

And the best part? The solution feels earned. You’ve turned a brute‑force nightmare into a tidy, readable loop that you can explain in a coffee break. That’s the kind of clarity that makes interviews feel less like a boss fight and more like a well‑timed combo move.

Your Turn

Grab a piece of paper (or your favorite IDE) and try solving Maximum Subarray Sum (Kadane’s algorithm) using the same mindset: identify the recurrence, keep only what you need, and iterate. Drop your solution or any questions in the comments—I’d love to see how you’ve leveled up your DP game!

Happy coding, and may the DP be with you. 🚀

Top comments (0)