Linear DP
What is this?
Walk along a row of choices, and at each step ask: what is the best answer that ends here? Not "the best answer overall" — that comes later, by taking the best of all the endings. Restricting the question to "ending exactly here" is what makes the recurrence local, because the only thing you need is the answer that ended at the previous position.
Once the state is defined that way, all four problems in this chapter are one line of arithmetic apiece.
💡 Fun fact: Maximum Product Subarray is the problem that breaks the habit built by every other one in this chapter. With sums, the running maximum is all you need. With products, multiplying by a negative swaps the roles of the largest and smallest — so the most negative running value is a candidate for the next maximum. Tracking
maxalone silently fails on[-2, 3, -4], whose answer is 24 and which no single-variable version will find.
🔓 The 5 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
The one-line idea: define
dp[i]as the answer ending ati, not the answer so far. That makes the recurrence depend on a couple of neighbours, and the overall answer becomes a maximum (or a sum) over the finished table.
1. Four recurrences
| Problem | State | Recurrence |
|---|---|---|
| Maximum Subarray | best sum ending at i |
dp[i] = max(nums[i], dp[i-1] + nums[i]) |
| House Robber | most money from the first i houses |
dp[i] = max(dp[i-1], dp[i-2] + nums[i]) |
| Decode Ways | decodings of the first i characters |
dp[i] = dp[i-1]·(valid 1-digit) + dp[i-2]·(valid 2-digit) |
| Max Product | best product ending at i |
needs two values — see below |
The Kadane recurrence is worth reading closely: max(nums[i], dp[i-1] + nums[i]) is exactly the question "is the run so far helping me, or should I start fresh here?" If dp[i-1] is negative, dragging it along can only hurt, so you restart.
2. When one value is not enough
best = cur_max = cur_min = nums[0]
for x in nums[1:]:
if x < 0:
cur_max, cur_min = cur_min, cur_max # a negative swaps the roles
cur_max = max(x, cur_max * x)
cur_min = min(x, cur_min * x)
best = max(best, cur_max)
The swap is the entire trick. Multiplying by a negative turns the most negative running product into the largest, so both must be carried and exchanged whenever the incoming value is negative.
3. Decode Ways is a counting DP
Rather than a maximum, dp[i] counts the ways to decode the first i characters. Two ways to arrive: take the last character alone if it is 1–9, or take the last two together if they form 10–26. Add the counts. The base case dp[0] = 1 represents the empty string having exactly one decoding, which is the identity for a product of counts rather than an arbitrary choice.
4. A 30-second worked example (Max Product)
nums = [-2, 3, -4]
The answer 24 comes from the whole array -2 × 3 × -4, and it was found only because -6 was still being carried when the -4 arrived.
5. Where you'll actually meet this
- Time-series analysis. Best-performing contiguous window of returns is Kadane, applied daily in finance.
- Signal processing. Maximum-energy segment of a waveform.
- Scheduling with exclusions. House Robber's "cannot take adjacent" is the shape of any non-adjacent selection problem.
- Encoding and parsing. Counting valid interpretations of an ambiguous string is Decode Ways — the same structure as counting parses in a grammar.
- Anomaly detection. Longest stretch whose aggregate exceeds a threshold.
6. Problems in this chapter
▶ Maximum Subarray Sum
Largest sum of a contiguous subarray. Kadane: extend the run or restart at the current element.
Pattern: best-ending-here. Target: O(n) time, O(1) space.
▶ House Robber
Maximum total from non-adjacent houses. Take this house plus dp[i-2], or skip it and keep dp[i-1].
Pattern: take-or-skip with a gap. Target: O(n) time, O(1) space with two rolling variables.
▶ Decode Ways
Count the ways a digit string maps to letters. Add the one-digit and two-digit continuations, respecting the 10–26 range and the leading-zero rule.
Pattern: counting DP with validity checks. Target: O(n) time, O(1) space.
▶ Maximum Product Subarray
Largest product of a contiguous subarray. Track both the running maximum and minimum, swapping them on a negative.
Pattern: dual-state linear DP. Target: O(n) time, O(1) space.
7. Common pitfalls 🚫
- Tracking only the maximum for products.
[-2, 3, -4]returns 3 instead of 24. - Initialising
bestto zero. For an all-negative array the answer is the least negative element, not 0. - Forgetting zeros in the product problem. A zero resets both running values;
max(x, cur * x)handles it naturally, which is why themaxwith the bare element matters. - Mishandling leading zeros in Decode Ways.
"0"decodes zero ways, and"06"is not a valid two-digit code. - Base case
dp[0] = 0for a counting DP. It must be 1 — the empty string has one decoding. - Returning
dp[n-1]for Kadane. The answer is the maximum over alldp[i], not the last one. - Writing the array when two variables suffice. Correct, and the follow-up is always "now in O(1) space".
- Paint Fence — count colourings where no three consecutive posts share a colour.
dp[i]splits into same as the previous post and different from it, and the recurrence falls out of that two-state split.
8. Key takeaways
- Define
dp[i]as "ending at i". Local endings make local recurrences. - Extend or restart is the whole of Kadane, and the question it asks is whether the past is helping.
- Products need two running values, exchanged whenever a negative arrives.
- Counting DPs use 1 as the empty-input base case, because it is the identity.
- The answer is often a maximum over the table, not its last entry.
- Roll the array into variables — every problem here is O(1) space.
- Why interviewers like it: the recurrences are short enough to write in two minutes, so the conversation is about your state definition and your edge cases.
Order: Maximum Subarray Sum → House Robber → Decode Ways → Maximum Product Subarray → Paint Fence.
Paint Fence
Core idea: This problem lives under BackTracking, but that filing is misleading — it's a counting DP, and presenting it honestly as one is the whole point. You're asked how many ways to paint
nposts, not to enumerate them, so you never build a single coloring; you just count. The rule "no three consecutive posts share a color" means two-in-a-row is fine, three is not. The trick is to realize that the only thing that matters about the painted prefix is whether the last two posts have the same color or not. So carry two running counts:same= number of valid colorings whose last two posts match, anddiff= number whose last two posts differ. Adding one more post: to make the new pair different you may follow any prior coloring (same or diff) with one of thek-1other colors →new_diff = (same + diff) * (k - 1); to make the new pair same (i.e. repeat the last color) you're only allowed to if the prior two differed — otherwise you'd create three in a row →new_same = diff. The answer issame + diff. One pass, two integers: O(n) time, O(1) space.
The problem, rephrased
You're staffing a row of n fence posts and you have k cans of paint (k distinct colors). The HOA has exactly one rule: no three posts in a row may be the same color. Two adjacent posts may match — a little doubling-up is fine — but a run of three identical posts is forbidden. The question isn't "show me a valid paint job," it's "how many distinct valid paint jobs exist?" Count them all.
Formally: given integers n (number of posts) and k (number of colors), return the number of ways to paint all n posts such that no three consecutive posts have the same color. Two posts in a row sharing a color is allowed; three is not.
This is LeetCode 276 — Paint Fence.
| Input | Means | Output |
|---|---|---|
n = 1, k = 3 |
One post, any of 3 colors | 3 |
n = 2, k = 2 |
Two posts, every pair allowed (no triple possible yet) | 4 |
n = 3, k = 2 |
Three posts, exclude the two all-same runs | 6 |
A note on the topic filing. You found this under BackTracking. Backtracking would solve it — try every color for every post, prune when you'd form a triple, count the leaves. But that's O(k^n) and throws away the obvious structure. This is a dynamic-programming counting problem: overlapping subproblems (the count for a prefix), optimal substructure (the count for
nposts is built from the count forn-1), and a clean state (do the last two match?). We'll show the backtracking baseline honestly, then replace it with the DP it's secretly asking for.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson