Dynamic Programming Notes

A printable cheat sheet built straight from this site's dynamic programming problem registry: the four questions every DP problem answers, then a state, recurrence, and complexity row for each worked problem.

The Four Questions

1. State

What does one table entry mean, in a full sentence, before any symbols. For example: dp[i] is the number of ways to reach stair i.

2. Recurrence

What final decision could produce this state. Each possible decision becomes a smaller subproblem; combine their answers to get the current one.

3. Base cases and dependency order

Which smallest states already have known answers, and which states must be solved before each later state can be computed.

4. Memo versus tabulation

Memoization solves only the states you need, top-down, with a cache. Tabulation fills every state bottom-up, in the dependency order from question three. The state and recurrence stay identical either way.

35 of 78 problems are worked in full below. The rest are listed at the end.

Linear 1D

ProblemStateRecurrenceLast decisionBases and orderTimeSpacePitfall
Fibonacci Numbersdp[i] is the ith Fibonacci number: the value of the sequence at position i, and nothing more. One number names the state, so the table is one row.dp[i] = dp[i - 1] + dp[i - 2]Ask what the last term added. Position i is defined as the sum of the two positions before it, so there is exactly one way to reach it and both predecessors contribute. Two buckets, added rather than compared, because there is no choice to optimize.dp[0] = 0 and dp[1] = 1 are given by definition; they cannot be derived. Every other cell depends only on smaller indices, so filling left to right guarantees both dependencies exist before they are read.O(n)O(n), reducible to O(1)Indexing off by one. Plenty of sources start the sequence 1, 1 rather than 0, 1, so dp[7] is 13 under one convention and 21 under the other. Fix the two base cases first and state them out loud before writing the loop.
Tribonacci Numbersdp[i] is the ith Tribonacci number: the value of the sequence at position i, and nothing more. One number still names the state, so the table is still one row.dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]Ask what the last term added, same as Fibonacci. Position i is defined as the sum of the three positions before it, so there is exactly one way to reach it and all three predecessors contribute. Three buckets, added, and the extra bucket is the only thing that changed.dp[0] = 0, dp[1] = 1, and dp[2] = 1 are given by definition; a third base case is needed because the recurrence now reaches three cells back. Every other cell depends only on smaller indices, so filling left to right still guarantees all three dependencies exist before they are read.O(n)O(n), reducible to O(1)The third base case. Some sources start Tribonacci 0, 0, 1 rather than 0, 1, 1, which shifts every later term. Pin down all three seed values from the problem statement before writing the loop; one wrong seed silently poisons the whole table.
Lucas Numbersdp[i] is the ith Lucas number: the value of this Fibonacci-like sequence at position i, and nothing more. One number names the state, so the table is one row, same as Fibonacci.dp[i] = dp[i - 1] + dp[i - 2]Ask what the last term added, the same question Fibonacci asks. Position i is defined as the sum of the two positions before it, so there is exactly one way to reach it and both predecessors contribute. The recurrence is identical to Fibonacci's; only the two seeds that start it differ, which is why every Lucas number can also be written as a sum of two Fibonacci numbers.dp[0] = 2 and dp[1] = 1 are given by definition, and they are the only difference from Fibonacci; they cannot be derived. Every other cell depends only on smaller indices, so filling left to right guarantees both dependencies exist before they are read.O(n)O(n), reducible to O(1)Reusing Fibonacci's seeds by habit. Starting this recurrence at 0, 1 instead of 2, 1 produces the Fibonacci sequence itself, term for term, since the two sequences share a recurrence but not a starting point. The seeds are the only thing this problem asks you to get right.
Climbing Stairsdp[i] is the number of distinct ways to reach stair i: the count of routes that land exactly there, and nothing more. One number names the state, so the table is one row.dp[i] = dp[i - 1] + dp[i - 2]The full derivation (why the last move splits every route into exactly two cases) lives in the flagship lesson at /courses/dynamic-programming/climbing-stairs. This page assumes it and goes straight to the table.dp[0] = 1 and dp[1] = 1 are given; see the flagship lesson for why standing still counts as one way. Every other cell depends only on smaller indices, so filling left to right guarantees both dependencies exist before they are read.O(n)O(n), reducible to O(1)Setting dp[0] = 0 because standing on the ground feels like doing nothing. But dp[0] means "one way to be already at the top with zero steps left", one way to stand still, and without it dp[2] comes out wrong: 1 + 1 = 2 only balances if dp[0] contributes.
Climbing Stairs with 3 Movesdp[i] is the number of distinct ways to reach stair i under this three-move rule: the count of routes that land exactly there, and nothing more. One number still names the state, so the table is still one row.dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]Ask what the last move could have been, the same question the flagship lesson asks. A move of 1, 2, or 3 steps is now legal, so the last move into stair i came from exactly one of three places: stair i-1, i-2, or i-3. Three buckets, added rather than compared, because there is no choice to optimize, only routes to count.dp[0] = dp[1] = 1 and dp[2] = 2 are given directly; a third base case is needed because the recurrence now reaches three cells back, and dp[2] would be wrong if it were derived from a two-term rule instead of stated outright (see the pitfall). Every other cell depends only on smaller indices, so filling left to right still guarantees all three dependencies exist before they are read.O(n)O(n), reducible to O(1)Treating dp[2] as derivable from dp[1] + dp[0], the way the flagship two-move staircase would. Under three moves dp[2] is still 2 (a single move of 2, or two moves of 1), but the three-term recurrence does not apply until i = 3; stating dp[0], dp[1], and dp[2] outright avoids folding a rule into a cell it was never meant to reach.
Weighted Climbing Stairsdp[i] is the cheapest total cost paid to land on stair i, where landing on a stair, including stair i itself, charges its own cost the instant you arrive: one running total, and nothing about which stairs came before it.dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i - 1]Ask what the last move into stair i could have been: a step of 1 from stair i - 1, or a step of 2 from stair i - 2. Either way you pay cost[i - 1] the instant you land on stair i, on top of whichever predecessor already cost less, so the recurrence takes a min over the two arrival routes and then adds that one landing cost once.dp[0] = 0 and dp[1] = cost[0] are given directly, and they pin the cost convention this table uses: cost is charged for LANDING on a stair, never for leaving one. Standing on the ground before any step is free, since nothing has been landed on yet, while dp[1] already carries cost[0], the price of arriving at stair 1. Every other cell depends only on smaller indices, so filling left to right guarantees both predecessors already hold a real value before they are read.O(n)O(n), reducible to O(1)Assuming cost is paid on leaving a stair instead of on landing on it. Sources disagree here, and the two conventions give different totals from the same cost array: under a leaving-cost reading, the top stair's own cost would never be charged, since there is nothing left to leave from, while under this table's landing-cost reading dp[0] carries no charge even though every stair after it does, because nothing was ever landed on at the ground floor. This table pays on landing: dp[0] = 0, and every dp[i] for i >= 1 includes cost[i - 1]. Compare against a source using the other convention and every value beyond dp[1] will look shifted.
Maximum Segmentsdp[i] is the maximum number of segments a length of i can be split into using only the allowed lengths, or a marker that no split exists at all: a count of pieces, and nothing about which lengths were chosen. That marker is reported as -1 once the answer reaches the outside world, but inside the table every unreachable cell carries its own version of it, not just the final one: a length that cannot be built at all needs to look impossible everywhere it shows up, not only when it happens to be the one being asked about, since a lone -1 sitting among ordinary segment counts could pass for a tiny real count instead of the warning it is meant to be.dp[i] = 1 + max(dp[i - a], dp[i - b], dp[i - c]) over whichever of those three predecessors is a valid, non-negative indexAsk what the very last segment cut off could have been. It has to be one of the three allowed lengths, so it leaves a remainder of i minus that length behind; whichever remainder already holds the most segments should be the one to extend, since the final cut always contributes exactly one more segment on top. That is a max over candidates, not a sum, because only one of the three final cuts actually happened.dp[0] = 0 is the only base: an empty remainder needs no segments at all, and it is a genuine answer, not a placeholder. Every cell from i = 1 up depends only on smaller indices (i - a, i - b, i - c, whichever are non-negative), so filling left to right guarantees each candidate already holds a real value, whether that value is a segment count or the impossible marker, before it is read. Lengths smaller than every allowed length (i = 1, i = 2 here) have no candidate at all: no length fits inside them, so they are impossible before the recurrence even runs.O(n)O(n), reducible to a rolling window covering the largest allowed lengthTreating an unreachable length as zero segments instead of impossible. Lengths 1, 2, and 4 cannot be built from 3, 5, and 7 at all, so dp[1], dp[2], and dp[4] are not zero, they are undefined states that must propagate as impossible so no later cell mistakes them for a valid, empty split. Folding an impossible predecessor to 0 would let a later cell wrongly count a phantom extra segment through a remainder that was never actually reachable.
Minimum Perfect Squaresdp[i] is the minimum number of perfect squares that sum to exactly i: a count of squares, and nothing about which squares were chosen. One number names the state, so the table is one row.dp[i] = 1 + min over every j with j*j <= i of dp[i - j*j]Ask what the last square added could have been. It is some j*j no larger than i, leaving a remainder of i - j*j behind, and whichever remainder already takes the fewest squares should be the one to extend, since the final square always adds exactly one more to the count. That is a min over candidates, not a sum, because only one final square actually got added.dp[0] = 0 is the only base: making zero takes no squares at all, and it is a real answer, not a placeholder. Every cell from i = 1 up depends only on smaller indices (i - j*j for every j with j*j <= i), so filling left to right guarantees every candidate already holds a real value before it is read. At i = 1 there is only one candidate, j = 1, so this cell is filled by a genuine minimum with nothing else to compare it against.O(n sqrt(n))O(n)Assuming a greedy, largest-square-first strategy works. It does not: 12 is 4 + 4 + 4, three squares, not 9 + 1 + 1 + 1, four squares, even though 9 is the largest square not exceeding 12. Every candidate remainder has to be compared through the full table; there is no shortcut that skips checking the smaller squares too.
House Robberdp[i] is the most money that can be collected robbing only among houses 0 through i, with no two adjacent houses both chosen: one running total, and nothing about which specific houses were picked.dp[i] = max(dp[i - 1], nums[i] + dp[i - 2])The full derivation, why the last house is either skipped or robbed with nums[i] added to dp[i - 2], lives in the flagship lesson at /courses/dynamic-programming/house-robber. This page assumes it and goes straight to the table.dp[0] = nums[0] and dp[1] = max(nums[0], nums[1]) are given; see the flagship lesson for why a single house is always taken and two adjacent houses reduce to picking the richer one. Every other cell depends only on smaller indices, so filling left to right guarantees both predecessors already hold a real value before they are read.O(n)O(n), reducible to O(1)Assuming a fixed alternate-houses pattern, rob every other house starting from house 0 or house 1, always wins. It does not: among the first six houses of this street (2, 7, 9, 3, 1, 8), robbing every even house gives 2 + 9 + 1 = 12 and robbing every odd house gives 7 + 3 + 8 = 18, but the true optimum is 19, robbing houses 0, 2, and 5 for 2 + 9 + 8, a gap pattern no fixed parity would ever try.
Decode Waysdp[i] is the number of ways to decode the first i digits of the string into letters: a count of valid splits, and nothing about which split. One number names the state, so the table is one row.dp[i] = dp[i - 1] (if the single digit at i is valid) plus dp[i - 2] (if the two digits ending at i are valid)Ask what the last decoded letter could have been. It is either the single digit at position i, valid whenever that digit is not '0', or the two digits ending at position i read together, valid whenever they form a number from 10 through 26. Both readings can be legal at once, in which case both prefixes contribute; either can fail on its own, in which case only the other contributes. The chosen string always leaves at least one reading legal at every position, so no cell is ever left with nothing to read at all.dp[0] = 1: an empty prefix has exactly one, vacuous, decoding. Every cell from i = 1 on depends only on dp[i - 1] and/or dp[i - 2], whichever readings are valid there, so filling left to right guarantees any dependency a cell needs already holds a real value before it is read.O(n)O(n), reducible to O(1)Treating a lone '0' as a normal digit that can stand on its own. No letter maps to '0', so wherever the digit itself is '0' (position 5 in this string), the single-digit reading is invalid and only the two-digit pair ending in that '0' can consume it. Skipping this check lets dp[5] wrongly add a phantom single-digit decoding of a digit with no letter at all.
Coin Change – Minimum Coins to Make Sumdp[a] is the minimum number of coins from {1, 3, 4} that sum to exactly a: a count of coins, and nothing about which coins were chosen. One number names the state, so the table is one row.dp[a] = 1 + min over every coin c <= a of dp[a - c]The full derivation, why the last coin spent leaves behind a remainder whose own fewest-coins count is already known, lives in the flagship lesson at /courses/dynamic-programming/coin-change. This page assumes it and goes straight to the table.dp[0] = 0 is given; see the flagship lesson for why making zero costs no coins. Every cell from a = 1 up depends only on smaller amounts (a - c for every coin c <= a), so filling left to right guarantees every candidate already holds a real value before it is read. Unlike maximum-segments, nothing here is ever unreachable: coin 1 is always available, so every amount can always be padded up to, only more or less cheaply.O(amount × coins.length)O(amount)Assuming the largest coin that still fits should always be spent first. It should not: with coins {1, 3, 4}, making 6 by greedily spending a 4 first leaves 2, which then costs two more 1s, for 4 + 1 + 1, three coins total, while the true optimum is 3 + 3, two coins. Every candidate remainder has to be compared through the full table; there is no shortcut that skips checking the smaller coins too.
Painting Fence Algorithmdp[i] is the number of ways to paint the first i fence posts with k colors so that no three consecutive posts share a color: a count of valid prefixes, and nothing about which specific colors were used. One number names the state, so the table is one row.dp[i] = (k - 1) × (dp[i - 1] + dp[i - 2])Ask what the last two posts could look like, not just the last one. Every valid painting of the first i posts either repeats post i-1's color once more, stretching a run to exactly two (there are dp[i-1] valid prefixes to extend this way, each extendable in k-1 further colors so the run never reaches three), or changes color entirely at post i (there are dp[i-2] valid prefixes of length i-2 to extend, again in k-1 ways, regardless of what closed each one off). Both cases scale by the same k-1, so they factor out as one multiplication over a sum rather than two separate additions.dp[0] = 1 is the vacuous case: an empty prefix has exactly one arrangement, and it is a real value, not a placeholder. But unlike dp[1] and dp[2], dp[0] is never read by any later cell: with three bases in place the first compute cell is i = 3, and it reads only dp[2] and dp[1]. dp[0] fills the table's leftmost slot because every index needs a value, not because the recurrence reaches back that far. dp[1] = k and dp[2] = k² earn their place for a genuinely combinatorial reason instead: the general transition only starts holding once a run of three is even possible, and at i = 2 every pair of colors is still allowed, so dp[2] is the full k² rather than the (k-1)-scaled value the formula would wrongly give (for k = 3, that would be (k-1)(dp[1]+dp[0]) = 2 × 4 = 8, not the true 9). Every cell from i = 3 on depends only on smaller indices, so filling left to right guarantees both real dependencies exist before they are read.O(n)O(n), reducible to O(1)Reading the constraint as 'no two consecutive posts share a color' instead of the real rule: only three consecutive posts of the same color are forbidden, so up to two in a row are fine. Post i is allowed to match post i-1's color; it just cannot also match post i-2's, or a run of three forms. Solving the stricter no-two-alike version gives a different, smaller count from post 3 onward.
Jump Gamedp[i] is the fewest jumps needed to land on index i, starting from index 0. It is a shortest path length measured in jumps, not a distance travelled, and it is defined only for indices you can actually reach; the rest are genuinely unreachable rather than free.dp[i] = 1 + min over j < i with j + nums[j] >= i of dp[j]Ask where the jump that landed on i came from. It came from some earlier index j, and the jump was legal only if j's own reach, j + nums[j], stretches to i or beyond. Whatever that j was, getting to it optimally is the same problem on a shorter prefix, so the cost is dp[j] plus the one jump you just took. Take the cheapest j among the legal ones. If no j reaches i at all, nothing lands there, ever.dp[0] = 0: you start on index 0, so it takes no jumps to be there. Fill left to right, because every j the recurrence reads is strictly smaller than i. Some cells have no legal j whatsoever, and those are unreachable, written as an infinity rather than a zero. In this array index 9 holds a 0, a dead end, and nothing before it reaches past index 9, so indices 10 and beyond can never be landed on no matter how large the table grows.O(n^2)O(n)Treating an unreachable index as costing zero jumps. An empty minimum is not 0, it is infinity, and if you seed the cell with 0 the whole table downstream reads as reachable in one cheap hop. The second trap is reading nums[i] as an exact jump length rather than a maximum reach. Every index strictly between j and j + nums[j] is a legal landing spot, which is precisely why one large value can flatten several cells to the same count.
Max A's using Special Keyboarddp[i] is the largest number of A's reachable using exactly i key presses in total, counting every press of every kind: one number for a budget, and nothing about which keys produced it.dp[i] = max over j of dp[j] * (i - j - 1)Ask where the last stretch of typing ended. Say the screen last grew by typing at press j. Everything after that is one select-all, one copy, and then nothing but pastes, because once you have copied there is no reason to type again: a paste is worth at least as much as a keystroke and usually far more. That leaves i - j presses, two of which are select-all and copy, so i - j - 2 are pastes. Each paste adds another copy of the buffer to a screen that already holds one, so the screen ends up multiplied by i - j - 1. Every choice of breakpoint j gives one candidate, and the best of them wins.Everything up to six presses is a base case, and for a reason worth seeing rather than memorising: a copy-and-paste cycle costs three presses before it pays anything back, so on a small budget plain typing simply wins. dp[i] = i for i up to 6, and only from seven presses on does a breakpoint beat typing. Each computed cell reads a whole range of earlier cells, from dp[1] up to dp[i-3], so filling left to right guarantees all of them exist. Note the table's first cells are read constantly by later ones, unlike most tables here where each cell looks back only a step or two.O(n^2)O(n)Assuming more copy-and-paste cycles always beat typing, and starting them as early as possible. A cycle costs three presses (select-all, copy, one paste) before it has gained anything at all, so on a short budget it loses outright, and even on a long one the best breakpoint is later than instinct suggests. The opposite mistake is just as common: assuming one cycle is always enough. It is not, for large n, and the recurrence quietly handles that because dp[j] may itself have been built by an earlier cycle.
Longest Increasing Subsequencedp[i] is the length of the longest strictly increasing subsequence that ends AT index i specifically, using nums[i] as its last chosen value: one count, tied to ending here, and nothing about which earlier elements were chosen.dp[i] = 1 + max over every j < i with nums[j] < nums[i] of dp[j], or 1 if no such j existsAsk what came immediately before index i in the best subsequence that ends there. Either nothing does, so index i starts a chain of its own, or some earlier index j with a strictly smaller value is the previous chosen element, in which case dp[i] extends whichever qualifying j already holds the longest chain. Every qualifying j is a candidate to extend, compared rather than summed, since only one of them actually precedes index i in the chain that ends here.dp[0] = 1 is the only declared base: with no index before 0, the first element is trivially a subsequence of length 1 all by itself, and it turns out to be read often, since most later values in this array are larger than nums[0] = 5. Every other index is a genuine compute cell, even the ones with no qualifying predecessor at all: dp[1] is exactly that case, since nums[0] = 5 is not smaller than nums[1] = 2, so dp[1] resolves to 1 on the strength of the same plus-one for standing alone, with no cell_read emitted, because there is nothing smaller before it to consult. Every other cell depends only on smaller indices, so filling left to right guarantees every predecessor that does qualify already holds a real value before it is read. Because the answer is whichever cell ends up largest, not necessarily the last one, the table also has to track that argmax as it fills, rather than simply reading off dp[8] at the end.O(n^2)O(n)Reading the last cell, dp[n - 1], as the answer. The longest increasing subsequence can end anywhere in the array, so here it peaks at dp[6] = 4 while dp[8] only reaches 3; the real answer is the maximum over the whole table. A second, unrelated mistake is assuming the subsequence must be contiguous: it only has to preserve relative order, so 2, 3, 6, 9 counts even though 8 and 6 sit in between 2 and 3 in the array.
Largest Divisible Subsetdp[i] is the size of the largest divisible chain that ends AT index i specifically, using nums[i] as its largest member: one count, tied to ending here, and nothing about which earlier numbers were chosen.dp[i] = 1 + max over every j < i with nums[i] % nums[j] == 0 of dp[j], or 1 if no such j existsAsk what the next-smaller number in the chain that ends at index i must have been. Since the array is sorted ascending, any earlier number that divides nums[i] evenly is a legal link one step down, so dp[i] extends whichever qualifying earlier index already holds the longest such chain. Checking only that nums[i] divides by a single earlier member is enough, because a chain built this way is automatically consistent all the way down: if a divides b and b divides c, then a divides c too, so the transition never has to re-check any link further back than the one directly below it.dp[0] = 1 is the only declared base: the smallest number is trivially a divisible chain of one all by itself, and because sorting places 1 there for this fixed array, it also ends up divisible by every later number, so it is read constantly (nums[0] = 1 divides everything). That happens to mean every later index always has at least one qualifying predecessor here, dp[0] itself, so this particular input never produces a zero-dependency compute cell beyond index 0; a differently chosen array could still have one, since the shape depends entirely on whether some earlier number divides the current one. Every other cell depends only on smaller indices, so filling left to right guarantees every candidate that does qualify already holds a real value before it is read. Because the answer is whichever cell ends up largest, not necessarily the last one, the table also has to track that argmax as it fills.O(n^2)O(n)Forgetting that sorting the array first is what makes a single divisibility check enough. Without sorting, checking only nums[i] % nums[j] == 0 for j < i would miss chains where a smaller-valued divisor happens to sit later in the input order; sorting ascending guarantees every legal chain reads as a run of increasing values from left to right, so the recurrence never needs to look both directions.
Weighted Job Schedullingdp[i] is the most profit obtainable from the first i + 1 jobs once they are sorted by end time, whether or not job i itself is taken: a running best over a prefix, not the profit of a schedule that must include job i.dp[i] = max(dp[i - 1], profit[i] + dp[p(i)])Ask what happened to the last job in the sorted prefix. Either you did not take it, in which case the best is whatever the previous prefix already achieved, or you did, in which case you collect its profit and add the best schedule that finishes before it starts. Those two cases cover every possibility with no overlap, so a max over them is the answer. The second case is the interesting one: p(i) is the latest job whose end time is at or before job i's start, which is almost never i - 2 and sometimes does not exist at all.The jobs must be sorted by END time before anything else happens, and that sort is what makes the recurrence valid: it guarantees every job compatible with job i sits somewhere to its left, so p(i) is always an index already filled. dp[0] is the first job's profit, since with one job the only choice is to take it. Filling left to right then works because both dp[i - 1] and dp[p(i)] are smaller indices.O(n^2) as shown, O(n log n) with binary searchO(n)Sorting by start time, or by profit, instead of by end time. Sorting by profit is the greedy instinct and it fails immediately: one fat job spanning the whole day beats nothing else, but two thinner jobs beside each other often beat it. Sorting by start time is subtler and looks fine until you notice it gives no guarantee that a compatible predecessor sits to the left, which is the single property the whole recurrence rests on.
Word Break Problemdp[i] is a yes-or-no flag, rendered here as 1 or 0: can the first i characters be split entirely into dictionary words? It is not a count of splits and not a length. The cell answers one question about a prefix, and the only thing later cells ever need from it is that answer.dp[i] = true if some j < i has dp[j] true and s[j..i) in the dictionaryAsk where the last word of the split begins. If the first i characters split legally, some word ends exactly at i, and that word starts at some j. Then s[j..i) has to be in the dictionary and everything before j has to split legally on its own, which is dp[j]. Try every j whose suffix is a dictionary word and take an or over them. Only j whose s[j..i) is actually a word are worth reading at all, and for many i there are none.dp[0] = true, the empty prefix, which splits vacuously into no words at all. That base is what lets any single dictionary word starting at position 0 turn its cell on. Fill left to right, since every j is smaller than i. Several cells here read nothing whatsoever, because no suffix ending at them is a dictionary word; those are 0 by default rather than by comparison.O(n^2) lookupsO(n)Stopping at the first prefix that splits and calling the whole string splittable. On this input the first seven characters split two different ways and the string still fails. The greedy cousin of the same mistake is committing to the longest dictionary word that matches at each position: take "cats" here and you strand "andog"; only backing up to "cat" gets you as far as index 7, and the table tries both without you having to notice.
Box-Stacking Problemdp[i] is the tallest stack achievable with rotation i sitting on top: one running height, tied to finishing with this particular rotation, and nothing about which other rotations are stacked beneath it.dp[i] = height[i] + max over every earlier rotation j whose base strictly contains rotation i's base in both dimensions, from a different box, of dp[j], or height[i] alone if no such j existsAsk what sat directly beneath this rotation in the tallest stack that finishes with it on top. It has to be an earlier rotation, sorted before it because its base area is at least as large, whose base strictly contains this rotation's base in both dimensions, and which comes from a different original box, since a box cannot rest on a different orientation of itself. Whichever qualifying rotation already carries the tallest stack beneath it is the one to build on, compared rather than summed, since only one rotation actually sits directly beneath this one.dp[0] = 5 is the only declared base: sorted by decreasing base area, the very first rotation has the largest base of any in the table, so no earlier, bigger base exists for it to rest on, and a stack of just this rotation alone stands as tall as its own height. That base, 9 by 8, only exists by rotating the fourth box onto its length as the new height, not the orientation it was given, and the cell does get read often afterward, by every later rotation whose base fits strictly inside 9 by 8 from a different box. Some compute cells still resolve with no read at all, the same zero-dependency shape as the other three problems in this batch: dp[3] is exactly that case, since the only rotation whose base numerically contains 8 by 5 is dp[0], and dp[0] comes from the same original box, so the box-identity check rules it out and dp[3] is left standing alone on its own height. That specific cell then turns out to anchor the entire optimal stack once later rotations from other boxes are free to build on it. Every other cell depends only on smaller indices, so filling left to right guarantees every candidate that does qualify already holds a real value before it is read. Because the answer is whichever cell ends up tallest, not necessarily the last one, the table also has to track that argmax as it fills.O(n^2)O(n)Only considering each box in the orientation it was given, instead of every box's three rotations. The largest base anywhere in this table, 9 by 8, belongs to a rotation of the fourth box that is not its given order: it comes from using that box's length as the new height, a genuine rotation away from [5, 8, 9]. Restricting every box to its given order would shrink the candidate pool before the recurrence even runs and could shrink the answer along with it. The other half of the same trap runs the opposite way: once a box is expanded, its rotations are still the same box, so no rotation may rest on another rotation of itself even when the bases fit. dp[3] is the visible proof, sitting alone with no read despite an 8 by 5 base that fits neatly inside the 9 by 8 above it.
Count Derangementsdp[i] is the number of derangements of i distinct objects: permutations in which no object lands back in its own original position, and nothing about which specific permutation achieves it. One number names the state, so the table is one row.dp[i] = (i - 1) × (dp[i - 1] + dp[i - 2])Ask what happens to the object that ends up in position 1: it cannot be object 1 itself, so there are i-1 choices for it. Say object k lands there. Now ask what object k's own position does: either object 1 moves into it, closing a 2-cycle and leaving a derangement of the remaining i-2 objects (dp[i-2] ways), or object 1 goes anywhere else that is not its own spot, which is exactly a derangement of the remaining i-1 objects with object k's slot relabeled as object 1's (dp[i-1] ways). Both cases are multiplied by the same i-1 choices for the first step, so they factor out as one multiplication over a sum.dp[0] = 1 and dp[1] = 0 are given directly: an empty arrangement is vacuously deranged, and the one permutation of a single object necessarily fixes it, so zero derangements exist. Every other cell, including dp[2], depends only on smaller indices and falls out of the very same transition (see the pitfall); filling left to right guarantees both dependencies exist before they are read.O(n)O(n), reducible to O(1)Assuming dp[1] = 0 means the recurrence has broken, or that dp[2] needs a hand-written special case the way painting-fence's dp[2] does. Neither is true here: dp[1] = 0 is the honest count of derangements of one object (none exist, since the lone object has nowhere else to go), and feeding dp[0] = 1 and dp[1] = 0 through that very same transition at i = 2 already produces the correct dp[2] = 1. No third base case is needed once the two seeds are right.
Longest Subsequence with 1 adjacent differencedp[i] is the length of the longest subsequence, with every consecutive pair differing by exactly 1, that ends AT index i specifically, using nums[i] as its last chosen value: one count, tied to ending here, and nothing about which earlier elements were chosen.dp[i] = 1 + max over every j < i with abs(nums[i] - nums[j]) == 1 of dp[j], or 1 if no such j existsAsk what value sat immediately before this one in the best run that ends at index i. It has to be some earlier index whose value is exactly one away, either one higher or one lower, since that is the run's only rule; whichever qualifying earlier index already holds the longest run is the one to extend. Every qualifying j is a candidate to extend, compared rather than summed, since only one of them actually precedes index i in the run that ends here.dp[0] = 1 is the only declared base: with no index before 0, the first element trivially starts a run of length 1 by itself, and it does get read later, at index 2, since nums[2] = 2 is exactly 1 away from nums[0] = 1. Every other index is a genuine compute cell, even the ones with no qualifying predecessor at all: dp[1] is exactly that case, since nums[0] = 1 is 99 away from nums[1] = 100, nowhere near a difference of exactly 1, so dp[1] resolves to 1 on the strength of the same plus-one for standing alone, with no cell_read emitted, because there is nothing one-away before it to consult. Every other cell depends only on smaller indices, so filling left to right guarantees every predecessor that does qualify already holds a real value before it is read. Because the answer is whichever cell ends up largest, not necessarily the last one, the table also has to track that argmax as it fills.O(n^2)O(n)Assuming the chosen values must sit next to each other in the array. They do not: here the two runs that actually differ by 1 (1, 2, 3, 4, 5, 6 and 100, 99, 98, 97, 96, 95) are interleaved one element apart the whole way through, so the longest subsequence has to skip every other element to stay on one run. A second version of the same mistake is reading the last cell as the answer instead of the largest cell in the table; both runs finish with a table value of 6, but the ascending run's tail reaches it one index earlier.
Word Wrap Problemdp[i] is the smallest total penalty for laying out the first i words, with a line break placed right after word i - 1. The index counts words consumed, not words indexed, so dp[0] is the empty prefix and the table has one more cell than there are words.dp[i] = min over j < i of dp[j] + cost(j, i)Ask where the last line of the prefix begins. Whatever word starts it, call that j, the words j through i - 1 sit on that line together and everything before j is a smaller version of the same problem. So the cost splits cleanly into dp[j] plus the penalty for that one line, and you take the best j. The only j worth trying are the ones where words j through i - 1 actually fit inside the width, which is a short contiguous run ending at i - 1.dp[0] = 0, the empty prefix, laid out for free. Fill left to right, because every j the recurrence reads is strictly smaller than i. cost(j, i) is the squared leftover space on a line holding words j through i - 1, counting one space between neighbours, or infinity if they do not fit. The one exception is the final cell: the last line of the whole text is not penalized, so cost(j, n) is 0 for every j whose words fit. That exception is what makes the last cell drop below its neighbour instead of climbing past it.O(n^2) worst case, O(n * words per line) in practiceO(n)Filling each line as full as it will go. It feels obviously right and it is wrong, because a line that fits one more word cheaply now can force a nearly empty line later, and the penalty is squared, so one badly stranded word outweighs several slightly loose lines. On these 8 words greedy pays 52 against an optimum of 28, and the entire difference is one line holding a single 3. The second trap is forgetting that the last line is free. Penalizing it is a different problem with a different answer, and the recurrence looks identical, so the mistake survives every test you would think to write.
Program for Bridge and Torch problemSort the crossing times ascending first. dp[i] is the minimum total time to get the i fastest people across with the torch ending on the far side. One number names the state only because of the sort: without it you would have to say which people are across, which is a subset, not a count.dp[i] = min(dp[i-1] + t[0] + t[i-1], dp[i-2] + t[0] + 2*t[1] + t[i-1])Ask how the slowest person still on this side gets over. Either they cross paired with the fastest, who then walks the torch back, which costs t[i-1] for the crossing and t[0] for the return on top of solving the smaller group, or the two slowest cross together and the two fastest handle the torch around them: the fastest pair goes over, the fastest returns, the slow pair crosses at t[i-1], and the second fastest returns. That second option counts only one of the two slow times, which is what makes it win once the slow people are far apart from the fast ones.dp[0] = 0, nobody to move. dp[1] = t[0], one person walks across alone. dp[2] = t[1], two people cross together at the slower one's pace, and nobody has to come back. Fill upward from i = 3, since both options read strictly smaller counts. The sort is not a convenience here, it is what makes t[0] and t[1] mean the fastest and second fastest at every step, which both options depend on.O(n log n) for the sort, O(n) afterO(n), reducible to O(1)Assuming the fastest person should always carry the torch back. It is the obvious rule and it is wrong exactly when the two slowest are much slower than the two fastest, because pairing the slow people makes one of their times disappear entirely, and that saving outweighs the extra return trip. dp[3] = 8 and dp[4] = 15 in this table are the two options trading places. The other trap is forgetting to sort, which quietly breaks t[0] and t[1] and therefore both options at once.

Grid 2D

ProblemStateRecurrenceLast decisionBases and orderTimeSpacePitfall
Min Sum in a Triangledp[r][c] is the smallest total of any descent that starts at the apex, steps down one row at a time onto one of the two numbers immediately below, and finishes ON the number at row r, column c, that number itself included. Two numbers name the state, the row and the column, so the table is a grid even though the input is a triangle. The cell holds a total and nothing about which columns the descent passed through; if you want the path itself you walk the table back up afterwards.dp[r][c] = tri[r][c] + min(dp[r - 1][c - 1], dp[r - 1][c])Ask which number the descent was standing on one row up. To arrive at (r, c) it can only have come from the number up and to the left, (r - 1, c - 1), or the number directly above, (r - 1, c), because those are the only two positions whose own two downward steps include this one. Whichever it was, the part of the descent above that point has to be a cheapest descent to that neighbour, which is the same question on a shorter triangle, and tri[r][c] is paid once either way. So take the smaller of the two and add this cell's own number. It is a min rather than a sum because exactly one of the two steps actually happened, the same reason min-cost-path takes a min where count-all-paths-in-a-grid adds. The ragged shape bites at the edges: column 0 has nothing up and to its left, and the last number of a row, column r, has nothing directly above it, so those cells have one candidate instead of two.dp[0][0] = tri[0][0] = 4: a descent that has only reached row 0 is standing on the apex and has paid for it, so the apex's own number is the whole of the cell. Every other cell of the triangle is computed. THE DOTS ARE NOT ZEROS, and this is the one thing on this page a learner can misread into a different answer. A grid has to be a rectangle, so the table is declared size by size, but only the cells with column <= row belong to the triangle; every position above the diagonal renders as a dot because it DOES NOT EXIST. If those dots were zeros they would be free numbers to stand on, and the cheapest descent would wander out into the empty half of the table and come back with a total far below the truth. Nothing is ever written to a dot and no cell ever depends on one, which is exactly why the edge cells have one genuine candidate rather than one real candidate plus one convenient zero. Filling row-major, left to right within each row, means both of the numbers a cell can descend from are final before it reads them, which is the only ordering property the recurrence needs.O(n^2) for n rowsO(n^2), reducible to O(n) with one rolling rowStepping onto the smaller of the two numbers below at every move. Greedy descent is the natural first instinct and it is wrong at every size from 3 rows up: on 3 rows it takes 4, 5, 5 for 14 while the true minimum is 4, 6, 2 for 12, and on the full 9 rows it pays 36 where the table pays 32. A small number is worth nothing if it is fenced in by large ones below it, which is precisely what a one-step-ahead rule cannot see. The second trap is reading the last cell of the bottom row as the answer; only the SMALLEST cell of the bottom row is, and here that is column 5 while the last cell holds 41. The third is treating the dots above the diagonal as zeros. They are positions that do not exist, and a zero there is a free number the descent would gladly cheat its way through.
Ways to Partition a Setdp[n][k] is the number of ways to split n distinct elements into exactly k non-empty, unordered subsets, so the cell holds a COUNT and nothing about which element went where. UNORDERED is the load-bearing word. The k subsets carry no labels and no order among themselves, so splitting three elements a, b, c into {a} and {b, c} is ONE partition however you write the two pieces down, not two. Two counts name the state, how many elements there are and how many subsets they have to fill, so the table is a grid even though there is no input array anywhere on this page. The nearest table in this course is binomial-coefficient's, and it is the same table SHAPE cell for cell: an (n + 1) by (n + 1) rectangle with everything above the diagonal skipped, the same two predecessors at every computed cell, the same row-major fill order, and the same base POSITIONS row for row, one per row down the left edge and one per row past the first on the diagonal, which is 17 of them at n = 8. Read that page first if you have not. TWO things then differ, and they are worth ranking rather than lumping together as one. The difference that matters to the algorithm is a single multiplication at each computed cell, and that multiplication is what this page is for. The difference that matters to anyone retyping the table is smaller and easier to get wrong: the positions match, but eight of the seventeen base VALUES at n = 8 do not, because past row 0 the left edge holds 0 here where it holds 1 there. dp[0][0] is 1 on both pages, and the diagonal is 1 on both; it is the eight left-edge cells below row 0 that disagree. basesAndOrder is where that edge is both explained and priced. pitfall prices the other mistake, the missing multiplication.dp[n][k] = k * dp[n - 1][k] + dp[n - 1][k - 1]Single out one particular element, say the last of the n, and ask what became of it. Either it ended up alone in a subset of its own, or it ended up sharing a subset with other elements. No partition is in both groups and none is outside them, so the two cases are disjoint and exhaustive and their counts add. TAKE THE ALONE CASE FIRST, because it is the one with no multiplier. If the singled-out element is by itself, the other n - 1 elements have to carry all of the remaining k - 1 subsets between them, and there are dp[n - 1][k - 1] ways for them to do that. Each of those arrangements yields exactly ONE partition of the n elements: bolt the lone element on as its own subset and you are finished, there is nothing left to choose. So this case contributes dp[n - 1][k - 1] as it stands. NOW THE OTHER CASE, WHICH IS WHERE THE COEFFICIENT COMES FROM. If the singled-out element is not alone, then pulling it out leaves every subset still non-empty, so the other n - 1 elements are already split into exactly k subsets, and there are dp[n - 1][k] ways for them to be. But that is NOT the number of partitions this case contributes, and assuming that it is is the mistake this page exists to prevent. Each of those k subsets is a home the element could have been dropped into. That is k choices, and each choice produces a DIFFERENT partition of the n elements, because the subsets are told apart by their contents and the element sits in a different one each time. Nothing is double counted either: given any partition in which the element is not alone, delete the element and you recover both the arrangement of the other n - 1 and which subset it came out of, uniquely. So the correspondence is k partitions of n to one arrangement of n - 1, in that direction, and the case contributes k × dp[n - 1][k]. WHY EXACTLY k, and not k! and not 2^k: the element goes into ONE subset, so it is a single choice among k, not a permutation of the subsets and not a selection of several of them. And the multiplier is the COLUMN index, the number of subsets, not the row index n. Both of those numbers are printed on the gutters right next to the cell, which is exactly why reaching for the wrong one is easy. It is a SUM between the two cases and a PRODUCT inside one of them because this is a counting problem: both cases really happen, across different partitions, and we want the total of them rather than the better of them. That is the same distinction binomial-coefficient draws against min-cost-path and it does not change here. What changes is that one of the two branches is not one outcome per arrangement but k of them.BOTH outer edges are given, as on binomial-coefficient's triangle, and one of the two holds a DIFFERENT NUMBER here. That difference is worth stopping on rather than skimming. dp[0][0] = 1. There is exactly one way to split the empty set into zero subsets: take no subsets at all. It is 1 rather than 0 because an empty collection of subsets is a legitimate arrangement of nothing, and it is the seed the whole triangle is built from. dp[n][0] = 0 for every row past the first, and binomial-coefficient's matching edge is 1. BOTH ARE RIGHT, and the reason is that the two cells count different things. There, the cell counts selections, and choosing NOTHING out of n items is a real selection, the empty one, which always exists and there is exactly one of it. Here the cell counts partitions of a non-empty set into zero non-empty subsets, and zero subsets hold no elements between them, so there is nowhere for the n elements to go and no such arrangement exists at all. One way to choose nothing from anything; no way to split something into nothing. That 0 is not decorative either. Every computed cell in column 1 reads it, which is what pins the whole of column 1 to 1 all the way down: dp[n][1] = 1 × dp[n - 1][1] + 0. Copy that edge across as a 1, which is the likeliest slip for a reader arriving from the other page, and column 3 comes out 7, 32, 122, 423, 1389 across n = 4 to 8 in place of the true 6, 25, 90, 301, 966. dp[n][n] = 1 for every row: there is exactly one way to split n elements into n non-empty subsets, every element alone in its own. Both edges have to be GIVEN rather than summed, and that reason is arithmetic rather than convention. dp[n][0] would reach for dp[n - 1][-1], column -1, which is off the table altogether, and dp[n][n] would reach for dp[n - 1][n], which sits above the diagonal and does not exist. Every cell with 1 <= k <= n - 1 is computed, and each of those has exactly two live predecessors, never one. At n = 8 the rectangle declares 81 cells, 45 of them live: 17 given, one per row down the left edge and one per row past the first on the diagonal, and 28 summed. THE DOTS ARE NOT ZEROS, in the sense that nothing is ever written to them and no cell ever reads one. A position with column > row would be asking to fill more non-empty subsets than there are elements to fill them, which is not a hard question, it is not a question. Nothing could read one by accident either: a computed cell at (n, k) has k <= n - 1, so the two cells it reads, (n - 1, k) and (n - 1, k - 1), both have column at most n - 1 and therefore sit on or below row n - 1's own diagonal. Zero is even the mathematically correct value for those positions, since there are no such partitions, so unlike min-sum-in-a-triangle nothing here would be corrupted by a stray zero above the diagonal. They are dots because they are not part of the question. Filling row-major, left to right within each row, means both cells a computed cell reads are final before it reads them, which is the only ordering property the recurrence needs.O(n^2) to fill the triangle down to row n, or O(n k) if the sweep stops at column kO(n^2), reducible to O(k) with one rolling row that stops at column kDropping the coefficient. Write dp[n][k] = dp[n - 1][k] + dp[n - 1][k - 1], which is binomial-coefficient's recurrence, and everything about the picture stays right: same triangle, same two arrows, same bases, nothing thrown, whole numbers throughout. What you get is Pascal's triangle SHIFTED one row down and one column right, not Pascal's triangle in place, because this page's left edge is 0 where Pascal's is 1. Measured, the broken table holds exactly C(n - 1, k - 1) at every cell with 1 <= k <= n <= 10. Three things let it survive a glance, and the numbers are what make the point. FIRST, it is not wrong everywhere. Of the 45 live cells at n = 8 it gets 24 right, and those 24 are precisely column 0, column 1 and the diagonal, the cells whose value is forced to a 0 or a 1 anyway. All 21 cells with 2 <= k <= n - 1 are wrong. The first cell it gets wrong is dp[3][2], where it says 2 and the truth is 3, so it has already failed AT n = 3, not somewhere past it. SECOND, the wrong answers are a recognisable sequence rather than obvious garbage: column 3 comes out 3, 6, 10, 15, 21 across n = 4 to 8, which are the triangular numbers, against the true 6, 25, 90, 301, 966. THIRD, and worst, one of the wrong answers is a right answer from next door. The broken table returns 6 at n = 5, and 6 is the correct value of S(4, 3), so a spot check at a single size can land on a number it has seen before and be reassured by it. The other two mistakes are about which index goes where, and both are worth measuring rather than waving at. Put the coefficient on the OTHER term, dp[n][k] = k * dp[n - 1][k - 1] + dp[n - 1][k], and column 3 comes out 10, 25, 46, 73, 106 across n = 4 to 8: wrong at four of the five sizes and exactly right at n = 5, where it returns 25. That is why the independent check behind this page runs every size the slider offers rather than one. Use the ROW index as the multiplier instead of the column and it is wrong everywhere and wildly so, 9, 71, 580, 5104, 48860 over the same range. And reading the two axes the wrong way round puts you in the empty half: S(8, 3) is dp[8][3], the 966, while dp[3][8] is a dot, because splitting 3 elements into 8 non-empty subsets is not a small number, it is not a thing at all.
Binomial Coefficientdp[n][r] is the number of ways to choose r items out of n distinct items, order ignored, so the cell holds a COUNT and nothing about which items were picked. Two counts name the state, how many there are to choose from and how many to choose, so the table is a grid even though there is no input array anywhere on this page. Set this next to min-sum-in-a-triangle, which fills a table with the same lower-triangular shape: there, every cell has a number of its own out of a given triangle, so the input contributes a term at every single cell. Here there is no input at all to read. A cell is fully described by the pair of counts labelling it, and its value comes from cells alone.dp[n][r] = dp[n - 1][r - 1] + dp[n - 1][r]Single out one particular item, say the last of the n, and ask whether a selection contains it. If it does, the remaining r - 1 picks come from the other n - 1 items, and there are dp[n - 1][r - 1] ways to make them. If it does not, all r picks come from the other n - 1 items, and there are dp[n - 1][r] ways. Every selection of r items falls into exactly one of those two groups, because it either holds that item or it does not, so the groups are disjoint and together exhaustive and the two counts simply add. It is a SUM rather than a min or a max because this is a counting problem: both cases really happen, across different selections, and we want the total of them, not the better of them. That is the same distinction as count-all-paths-in-a-grid adding where min-cost-path takes a min.BOTH outer edges are given, and the reason is arithmetic rather than convention. dp[n][0] = 1 for every row: there is exactly one way to choose nothing, the empty selection, and it always exists. It has to be a base because the cell the recurrence would send it to is dp[n - 1][-1], column -1, which is off the table altogether. dp[n][n] = 1 for every row: there is exactly one way to choose all n items, take every one of them. It has to be a base because the cell the recurrence would send it to is dp[n - 1][n], which sits above the diagonal and does not exist. Every cell with 1 <= r <= n - 1 is computed, and each of those has exactly two live predecessors, never one. THE DOTS ARE NOT ZEROS, in the sense that nothing is ever written to them and no cell ever reads one: a grid has to be a rectangle, so the table is declared n + 1 by n + 1, but only the cells with column <= row belong to the triangle, and a position with column > row would be asking for more items than the row has to offer. Here that matters less than it does in min-sum-in-a-triangle, and the difference is worth being precise about rather than borrowing that page's alarm. There, a zero above the diagonal would be a free number a descent could cheat through, and the answer would come out below the truth. The two pages depend on the same two directions, up-and-to-the-left and straight up, so that is not the difference. The difference is that this page makes the DIAGONAL a base while the triangle computes it: a computed cell here reads dp[n - 1][r - 1] and dp[n - 1][r], and since r <= n - 1 both of those are on or below the diagonal, so the empty half is unreachable from any live cell. Zero is even the mathematically correct value for those positions, since there are no ways to choose more items than exist. The triangle has no such guarantee, because its diagonal cells are computed rather than given, and the straight-up read from one of those lands in the row above, which is a column shorter; that is the read its spec has to leave out by hand. They are dots because they are not part of the question, not because a zero there would corrupt anything. Filling row-major, left to right within each row, means both cells a computed cell reads are final before it reads them, which is the only ordering property the recurrence needs.O(n^2) to fill the triangle up to row n, or O(n r) if the sweep stops at column rO(n^2), reducible to O(r) with one rolling rowComputing it as n! / (r! (n - r)!). The formula is correct on paper and wrong in a fixed-width number type, and it fails far earlier than anyone expects: 19! is already past the largest integer a double represents exactly, so in JavaScript that route hands back 253.00000000000003 for C(23, 2), whose true value is the small integer 253, and by C(171, 2) the numerator has overflowed to Infinity while the answer itself is only 14535. The additive table never divides and never leaves the integers, which is the whole reason to prefer it at these sizes. The second real mistake is reading the two axes the wrong way round: C(6, 3) is dp[6][3], the 20, while dp[3][6] is a dot, because choosing 6 items out of 3 is not a small number, it is not a thing at all. If a lookup lands above the diagonal, the arguments went in swapped.
Nth Row of Pascal Triangledp[n][k] is entry k of row n of Pascal's triangle, which is the number of ways to choose k items out of n. That is the SAME TABLE binomial-coefficient fills, cell for cell: same lower-triangular shape, same two given edges, same rule at every interior cell, same row-major order. One thing changed, and it is the question asked of the table. There, the input named a pair of counts and one cell answered it. Here the input names a row, so the answer is a WHOLE ROW: all n + 1 cells of the bottom row are output, and the sweep's final pass is the result rather than a step towards it. A reader arriving here first loses nothing by reading it in this order: a cell holds a count and nothing about which items were picked, and the two numbers labelling it, the row and the position within that row, are the whole of the state.dp[n][k] = dp[n - 1][k - 1] + dp[n - 1][k]Single out one of the n items and ask whether a selection of k contains it: the selections that keep it need k - 1 more from the other n - 1 items, the selections that drop it need all k from the other n - 1, no selection is in both groups and none is outside them, so the two counts add rather than compete. binomial-coefficient makes that argument in full, including why a counting problem sums where an optimization problem takes a min, and not one word of it changes here. What is worth adding is what the same rule says once you are after the whole row: row n is determined by row n - 1 ALONE, one adjacent pair of it at a time. The rule never reaches back further than a single row, so every row above that one is dead the moment the row below it has been written. That single observation is what the execution section below spends its whole length on.dp[n][0] = 1 and dp[n][n] = 1 on every row, one way to choose nothing and one way to choose everything, and both must be given rather than summed because the cells the rule would reach for, column -1 at one end and a position above the diagonal at the other, are off the table. Now say what that means HERE, because it is not what it means on binomial-coefficient's page, where the bases are the two places the answer is not. TWO OF THE NUMBERS THIS PAGE RETURNS ARE BASES. The 1 at each end of row 8 was not worked out by the sweep, it was handed to it, and only the 7 interior entries of the answer row are computed at all. Of the 45 live cells at n = 8, 17 are given, one per row down the left edge and one per row past the first on the diagonal, and 28 are summed. The positions above the diagonal show a dot, meaning a position that does not exist rather than a zero, and nothing here could read one even by accident: a computed cell in row r at column k has k <= r - 1, so the two cells it reads are columns k - 1 and k of row r - 1, both on or below that row's own diagonal. The FILL ORDER is that page's order too, and it is what this page's whole objection is about. Row-major, top to bottom and left to right inside each row, is enough for the rule, though it is not the only order that would do (column-major satisfies the same dependencies), and it is exactly the order the statement objects to: reaching row 8 this way means writing all 36 cells of rows 0 through 7, and 21 of those are additions this order performs before the first interior entry of row 8 is reached. None of the 36 is wanted. The order is not wrong. It is the cost, and execution is where that gets settled.O(n^2) additions to sweep the triangle down to row n, against O(n) multiply-divide steps for the in-row closed formO(n^2) for the full table, O(n) with one rolling row, which is the size of the answerUpdating the single rolling row from the LEFT. It is the one step of the reduction that has a direction, and going the wrong way fails silently: nothing throws, the values stay whole numbers, and they are simply the wrong whole numbers. Writing row[k] = row[k] + row[k - 1] with k running upwards means position k - 1 has already been overwritten with the new row's value by the time position k reads it, so the cell adds its NEW left neighbour instead of the old one the rule asked for. At n = 8 that returns 1, 8, 35, 110, 275, 572, 1001, 1430, 1430 where the row is 1, 8, 28, 56, 70, 56, 28, 8, 1: too large from column 2 rightwards, no longer symmetric, and no longer even ending in a 1. What makes this survive a glance is that the wrong numbers are not noise. Run the broken loop for n = 1 through 8 and its last entry comes out 1, 2, 5, 14, 42, 132, 429, 1430, which are the Catalan numbers, so what comes back is a real and recognisable integer sequence rather than obvious garbage. Run the inner loop downwards from k = i instead and every cell reads the old value it wanted, with no second buffer. A smaller trap worth naming: the 1s at the two ends of the row are given, not computed, so a loop that tries to compute every entry of row n uniformly will ask for column -1 at one end and a position above the diagonal at the other.
Min Cost Pathdp[r][c] is the cheapest total cost of any right-and-down route from the top-left corner to cell (r, c), counting every cell the route stands on. Two numbers name the state, the row and the column, so the table is a grid rather than a row. The cell holds a price and nothing about the route that achieved it; if you want the route itself you walk the table backwards afterwards.dp[r][c] = cost[r][c] + min(dp[r - 1][c], dp[r][c - 1])Ask which step landed on (r, c). Only two are legal: down from (r - 1, c), or right from (r, c - 1). Whichever it was, the rest of that route has to be a cheapest route to that neighbour, which is the same question on a smaller corner of the grid, and the final step is charged cost[r][c] either way. So take the cheaper of the two neighbours and add this cell's cost once. It is a min rather than a sum because exactly one of the two steps actually happened. count-all-paths-in-a-grid asks about the same two neighbours and adds them instead, because there every route counts rather than competing.dp[0][0] = cost[0][0], and that is a CONVENTION rather than something the statement forces. The statement says a cell has a cost to enter, and you never enter the cell you begin on, so a reading where the start is free is perfectly defensible; it just gives a different number at every size. This page charges for the start, because that is what almost every published version of the problem does, so a learner checking the answer against another source sees the same total. If you meet the other convention, subtract cost[0][0] from every cell. Row 0 has nothing above it and column 0 has nothing to its left, so those cells have one candidate instead of two and come out as running totals along the two edges. Filling row-major, left to right within each row, means the cell above and the cell to the left are both already final before any cell uses them, which is the only ordering property the recurrence needs.O(rows × cols)O(rows × cols), reducible to O(cols) with one rolling rowStepping onto whichever neighbour is cheaper. From the start of this grid the cell below costs 1 and the cell to the right costs 2, so a greedy walk goes down, and that 1 is fenced in by an 8 and a 9: greedy pays 21 where the table pays 17. A cheap cell is worth nothing unless its neighbours are cheap too, which is precisely what a local rule cannot see and what the table prices for you. The second trap is the base. Decide out loud whether dp[0][0] is cost[0][0] or 0 before filling a single cell, because both conventions are in circulation and they disagree at every cell by exactly cost[0][0].
Count all paths in a Griddp[r][c] is the number of distinct right-and-down routes from the top-left corner to cell (r, c). Two numbers name the state, the row and the column, so the table is a grid rather than a row, and the cell holds a count with nothing in it about which routes were counted.dp[r][c] = dp[r - 1][c] + dp[r][c - 1]Ask what the last step onto (r, c) was. It was either a step down from (r - 1, c) or a step right from (r, c - 1). Those two sets of routes can never overlap, because they differ in their final step, and between them they are every route that arrives here at all. So the count is the sum of the two, not a choice between them. min-cost-path asks this same question about these same two neighbours and takes a min instead, because there the routes compete on price; here they are all counted.dp[0][0] = 1: standing on the start is one route, the empty one. Every other cell of row 0 and of column 0 is 1 as well, and those are worth declaring out loud rather than deriving: a cell in the top row has no cell above it to come from, so the only route into it is the single straight run of steps right, and the first column is the same story with steps down. Filling row-major, left to right within each row, means the cell above and the cell to the left already hold their final counts before any cell uses them, which is the only ordering property the recurrence needs. This grid keeps its 6 rows fixed and lets the slider change the number of columns, so you can watch the table get wider one column at a time while its row count, and with it the number of downward steps every route has to take, stays put.O(rows × cols)O(rows × cols), reducible to O(cols) with one rolling row, or O(1) via the closed formCounting steps rather than orderings, and then getting the closed form off by one. Every route across a 6 by 4 grid takes exactly 5 + 3 = 8 steps, so there is nothing here to optimize and no route is shorter than another; the only question is how many orders those 8 steps can come in. And both numbers in the closed form count steps rather than rows and columns: the top is the total number of steps, (rows - 1) + (cols - 1), and the bottom is how many of those steps go in one chosen direction. A 6 by 4 grid takes 5 downs and 3 rights, so it is C(8, 5) = 56, never C(10, 6) and never C(5, 3). The table is the safer place to start precisely because it never asks you to get that right.
Paths in a Grid with Obstaclesdp[r][c] is the number of right-and-down routes from the top-left corner to cell (r, c) that never stand on an obstacle. That is the same two-number state as the unobstructed grid: the obstacles change what a cell is allowed to be, not what the cell means. Read a 0 carefully, because two different situations produce one: a blocked cell is 0 because no route may occupy it, and a clear cell is 0 when no route manages to reach it. The table prints the same digit for both, so keeping them apart is on you.dp[r][c] = 0 if (r, c) is blocked, otherwise dp[r - 1][c] + dp[r][c - 1]For a clear cell, ask exactly what count-all-paths-in-a-grid asks: the last step was either down from above or right from the left, the two sets of routes are disjoint, so add them. The obstacle check comes first and cancels the question entirely. If no route may stand here, there is no last step to ask about, and the count is 0 without either neighbour entering into it. Where the zero comes from is the thing to hold on to: a blocked cell is zero by rule, and a clear cell is zero only when the ways into it sum to zero, one way in along an edge and two everywhere else, which means every way in is itself dead.dp[0][0] = 1 is the only base, and that single difference from the unobstructed grid is worth dwelling on. There the whole first row and first column can be declared 1 up front; here they cannot, because one obstacle in row 0 makes every cell to its right unreachable, so the edges have to be computed like every other cell, just with one candidate instead of two. Filling row-major keeps the cell above and the cell to the left final before they are used. A blocked cell is the one write in this table that depends on nothing at all: it has no cells to consult, so the walkthrough does not stop and ask you to predict it, because there is no arithmetic there to predict.O(rows × cols)O(rows × cols), reducible to O(cols) with one rolling rowReading every 0 in the table as an obstacle. In the 5 by 5 table, row 4 column 1 holds 0 and is perfectly walkable; both of its ways in are blocked, so no route reaches it, and it then hands that 0 on to its own neighbours like any ordinary value. Whole regions of clear cells can go dark like this behind a couple of well-placed obstacles. The other half of the trap is muscle memory on the edges: filling the first row and first column with 1s is right only until an obstacle appears in one of them, and seeding 1s past that obstacle invents routes that walk straight through a wall.
Permutations with K Inversionsdp[r][k] is the number of permutations of 1 through r + 1 that contain exactly k inversions, an inversion being a pair of positions holding values in the wrong order, larger before smaller. THE ROW INDEX IS ONE LESS THAN n, and getting that straight is the single thing most likely to make every number on this page look wrong. Row 0 is n = 1, row 1 is n = 2, row r is n = r + 1, and the table therefore declares size rows rather than size + 1, because n = 0 answers nothing anybody asked. It is not the only 2D table in this course that offsets an index, so treat no row gutter as self-evident: read the row heading on whatever page you are on before trusting the numbers down the side. The cost of missing it is concrete: at n = 7 the bottom row is row 6, and a reader who takes row 6 for n = 6 reads 169 where the answer for n = 6 is the 71 one row up. Two numbers name the state, how many values are being permuted and how many inversions they have to hold, so the table is a grid even though there is no input array anywhere on this page; that much it shares with binomial-coefficient and ways-to-partition-a-set. THE SHAPE IS WHERE IT PARTS COMPANY WITH THEM. Those two declare a square and skip the half above the diagonal, as do nth-row-of-pascals-triangle and min-sum-in-a-triangle: measured, four of the eight 2D tables authored before this one are lower-triangular in exactly that way, all four skipping the cells with column greater than row, and a dot on one of them means a position that does not exist. This table is a FULL RECTANGLE with every declared cell live and not a single dot, and its width does not grow with n past 12 columns. The cell holds a count and nothing about which orderings achieved it.dp[r][k] = sum over j = 0 to min(k, r) of dp[r - 1][k - j]Take the largest of the n values and ask where it ended up. Nothing outranks it, so every inversion it belongs to is an inversion with something to its RIGHT, one for each value sitting after it. Put it last and it contributes none. Slide it one position forward and it contributes one. Put it at the very front and all n - 1 of the others are to its right, so it contributes n - 1. That is n placements contributing 0, 1, up to n - 1 inversions, one value of j apiece. NOW THE PART THAT MAKES IT A RECURRENCE. Delete the largest value and what is left is a permutation of the other n - 1, in the same relative order, holding exactly the inversions it held before: none of them involved the largest value, so none of them is disturbed by where it sat. So if the largest contributed j, the rest have to supply the remaining k - j between them, and the row holding permutations of n - 1 values already counts the ways to do that. Mind which row that is: this cell lives in row n - 1, so the row it reads is row n - 2, one above it, and in the table's own coordinates that is simply dp[r - 1][k - j]. The correspondence runs both ways with nothing left over. From a permutation of n you read off one j and one permutation of the rest; from a j and a permutation of the rest you rebuild exactly one permutation of n. So the counts add, and they add over a WINDOW rather than a pair, which is the whole difference between this page and the three Pascal-shaped tables it sits near. TWO SEPARATE THINGS CAP THE WINDOW and both are real. j cannot exceed the number of positions the largest value can move forward through, which is n - 1, and in table coordinates n - 1 is the ROW INDEX r. j also cannot exceed k, because the rest of the permutation cannot supply a negative number of inversions to make up the difference. So j runs 0 to min(k, r) and the window is min(k, r) + 1 cells wide. It is a SUM rather than a min or a max because this is a counting problem: every placement really happens, across different permutations, and the total of them is what is wanted rather than the best of them. That is the same distinction count-all-paths-in-a-grid, two problems up this file, draws against min-cost-path.THE WHOLE OF ROW 0 IS GIVEN, all of it, not merely its first cell. Row 0 is n = 1 and there is no row above it, so nothing in it could be summed from anywhere. dp[0][0] = 1, because the one ordering of a single value has no inversion: an inversion needs two positions to disagree and there is only one position. dp[0][k] = 0 for every k from 1 to the right edge, because one value cannot be out of order with anything. At n = 7 that is 12 given cells, one per declared column. COLUMN 0 IS GIVEN ON EVERY ROW TOO, value 1, and that is a choice with a reason on each side. The value itself is not in doubt: exactly one permutation of any n has no inversions at all, the sorted one. What is in doubt is whether to compute it, and the arithmetic settles that. The window for column 0 is min(0, r) + 1 = 1 cell wide, so the recurrence would copy dp[r - 1][0] across and call it a step, and a one-term sum restates a value rather than deriving one. Saying the sorted permutation out loud says strictly more. At n = 7 that is 6 further given cells, one per row past the first, so 18 of the table's 84 cells are given and the other 66 are summed. THE TABLE IS CUT OFF ON THE RIGHT, AND THE CUT IS A PROOF RATHER THAN A NOTE. A permutation of n values holds at most n(n - 1)/2 inversions, which is 21 at n = 7, so a complete table would need 22 columns; this one declares 12, numbered 0 through 11. Here is why nothing surviving is damaged. Every cell dp[r][k] reads only cells dp[r - 1][k - j] with j at least 0, so every cell it reads sits in its own column or a column to its LEFT. No cell anywhere in this table ever reads a column to its right. Removing columns 12 and beyond therefore removes cells that no remaining cell could ever have consulted, and every cell left on screen holds exactly the value it would hold in the full 22-column table. The answer cell, column 5, is nowhere near the cut. NOW THE HONEST HALF: the rows themselves are incomplete, visibly so at the bottom. At n = 7 five of the seven rows shown are complete, n = 1 through n = 5, whose largest inversion counts are 0, 1, 3, 6 and 10 and all fit inside column 11. The other two are truncated, n = 6 running to 15 and n = 7 to 21. What that costs you is a picture rather than a value: every complete row is symmetric about its middle, 1, 4, 9, 15, 20, 22, 20, 15, 9, 4, 1 at n = 5, because reversing a permutation turns each of its inversions into a non-inversion and back, so k and n(n - 1)/2 - k always tie. A cut row shows that symmetry only up to where it stops. As it happens neither cut hides its row's peak: n = 6's twin 101s sit at columns 7 and 8 and n = 7's twin 573s at columns 10 and 11, both pairs on screen, and what falls off the right edge is only the descent back to a single 1. THE FILL ORDER is row-major, left to right within each row, and the window makes the ordering requirement weaker rather than stronger. Every cell a computed cell reads lies in the row DIRECTLY above it, never in its own row, so finishing each row before starting the next is the whole of what the recurrence needs; within a row the order is free, because no cell in a row depends on another cell in the same row.O(n × k × min(k, n)) to fill the table, since each of the (n - 1) × k computed cells sums a window of at most min(k, n - 1) + 1 cells; O(n^2 k) as a loose boundO(n × k), reducible to O(k) with one rolling rowThe window width. There are THREE ways to get the width itself wrong, they fail in completely different registers, and they are worth ranking rather than lumping together; then there is a fourth mistake that is not about the width at all, and it is the one this table protects you from least. TWO OF THE FOUR CAN FAIL SILENTLY, which is the ranking that matters. USING min(k, n) WHERE THE TRUTH IS min(k, n - 1). This grants the largest value one more position than it has, and it is loud rather than subtle: measured, it reports 11, 41, 105 and 224 across n = 4 to 7 against the true 3, 22, 71 and 169, wrong at every size by a factor of 3.667, 1.864, 1.479 and 1.325, and the first cell it gets wrong is dp[1][2], where it claims one ordering of two values holds two inversions. Nothing about it looks fine at small n; it has already failed by the second row. USING min(k, n - 2) IS THE DANGEROUS ONE, and it is dangerous at every size rather than at some of them. It reports 0, 3, 22 and 71 across the same four sizes, and ALL FOUR are the correct answer for the next size down: measured, that variant's bottom row is exactly the true row for n - 1, cell for cell, at every one of n = 4, 5, 6 and 7. The 0 it reports at n = 4 is no exception, because a permutation of 3 values cannot hold 5 inversions, so 0 is the right answer to the question it has quietly substituted. This is not a table that looks broken; it is a table that looks perfect and answers a question one size down. A spot check anywhere lands on a number it has seen before and is reassured by it, which is why the independent check behind this page runs every size the slider offers rather than one. FORGETTING THE min WITH k ALTOGETHER, so j runs to n - 1 and the sum reaches for a negative column. This one is not a wrong-number bug at all, and saying it is would be wrong. Every out-of-range term is a term the true sum did not want, so if reading past the left edge contributes nothing the table is unchanged; in JavaScript dp[r - 1][-1] is undefined instead, so total += undefined poisons that cell to NaN. Measured, the NaN reaches the reported answer at n = 5, 6 and 7 but not at n = 4, where the poisoned cells happen to sit outside the answer's cone. It is a crash-shaped mistake, not a plausible-numbers one. READING THE TWO AXES THE WRONG WAY ROUND is the fourth mistake, the one that is not about the window at all, and the second of the four that can fail silently. This table gives you less protection against it than the triangular pages do. On binomial-coefficient a swapped lookup lands above the diagonal, on a dot, and tells you so at once. Here every cell of the rectangle is live, so a swap lands on a real number: at n = 7 the answer is dp[6][5] = 169 and the transposed cell dp[5][6] = 90, which is a perfectly good count of something else, the orderings of 6 values with 6 inversions.
Tile Stacking Problemdp[r][j] is the number of ways to colour the first r + 1 tiles from the 3 colours so that no run of one colour anywhere among them is longer than 3 AND the run the tiles FINISH on is at most j long. THE ROW INDEX IS ONE LESS THAN THE TILE COUNT: row 0 is 1 tile, row r is r + 1 tiles, so the table declares size rows rather than size + 1, because a row of no tiles answers nothing anybody asked. Two numbers name the state, how many tiles have been coloured and how long a run they are allowed to finish on, so the table is a grid even though the input is a single number and there is no array anywhere on this page. AT MOST IS THE LOAD-BEARING PHRASE, AND IT IS NOT A TRICK. The state you would write down first is exactly j, the colourings whose final run has length precisely j, and it is a perfectly good state with a perfectly good recurrence. Its answer, though, is the SUM of the bottom row rather than any one cell of it, because a legal colouring of the whole row ends on a run of 1, 2 or 3 and all three count. This page reports one highlighted cell, so a total across a row is not something it can point at. Making the state cumulative fixes that at no cost: once a cell counts every final run up to j, the cell at j = 3 has already done the summing, and dp[size - 1][3] IS the answer outright. Three separate measurements back that up rather than one. The cumulative table equals the running row-total of the exact-run table, cell for cell, at every size the slider offers; the exact-run table's bottom row sums to the same answer as the cumulative table's last cell at every one of those sizes; and the cumulative recurrence was checked against brute-force enumeration of all 3^n colourings at every n from 1 to 9, agreeing exactly. THAT REFORMULATION IS THE TRANSFERABLE PART OF THIS PAGE and it is worth more than the recurrence. Whenever the natural answer is a fold across a row, look for an axis along which at most is meaningful and push the fold into the state. The axis here is a LENGTH, which is ordered, so the move is available. It is not always available: an axis of unordered labels, a colour or a common difference, has no at most to accumulate along, and a page whose answer folds over such an axis cannot be rescued this way. Two smaller things the cell does NOT hold. It holds a count, so nothing here tells you which tile got which colour. And column 1 is NOT no two neighbours ever match: it is every legal colouring whose last two tiles differ, with the tiles before them free to run up to 3. Those are different questions and different numbers, 3888 against 384 at 8 tiles, and running them together is the easiest wrong reading of this axis.dp[r][j] = (m - 1) * dp[r - 1][k] + dp[r - 1][j - 1], which at m = 3 and k = 3 is 2 * dp[r - 1][3] + dp[r - 1][j - 1]Ask what the LAST tile of the row does. It either takes a different colour from the tile before it or it repeats that colour. Every colouring does exactly one of those two things, so the two cases are disjoint and together exhaustive and their counts add. TAKE THE REPEAT CASE FIRST, because it is the one with no multiplier. If the last tile repeats, the run it joins is one longer than the run the shorter row ended on, so that shorter run had to be at most j - 1 long for this one to come in at at most j. There are dp[r - 1][j - 1] colourings of the first r tiles that finish that way, and each of them yields exactly ONE colouring of r + 1 tiles: the colour is forced, it is the one already there, and there is nothing left to choose. So this case contributes dp[r - 1][j - 1] as it stands. NOW THE CHANGE CASE, WHICH IS WHERE THE COEFFICIENT COMES FROM. If the last tile takes a different colour it starts a fresh run of length 1, and 1 is at most j for every column this table computes, so the new run cannot be the thing that breaks the rule. What came before is therefore under NO constraint beyond being legal in its own right, and legal in its own right is exactly what column k holds: dp[r - 1][3] is every colouring of the first r tiles, whatever run it ends on. Each of those can be followed by any of the m - 1 = 2 colours that are not the one on tile r, and each choice is a DIFFERENT colouring, so this case contributes 2 × dp[r - 1][3], not dp[r - 1][3]. Nothing is double counted either: given a colouring of r + 1 tiles whose last two tiles differ, delete the last tile and you recover both the shorter colouring and which of the two other colours was used, uniquely. WHY m - 1 AND NOT m: the last tile is choosing a colour DIFFERENT from its neighbour, so one of the three is ruled out. Use 3 and the answers come out 189, 747, 2952, 11664 and 46089 across 4 to 8 tiles against the true 78, 228, 666, 1944 and 5676, wrong at every size and off by a factor of more than eight at the top. AND WHY THE FACTOR SITS ON THE COLUMN-3 CELL rather than on the column-j one: reach for dp[r - 1][j] instead and you have quietly required the earlier run to be short as well, which the change case does not require. That is the dangerous mistake on this page, because it is RIGHT at 4 tiles, returning 78, and wrong at the other four sizes, returning 216, 576, 1488 and 3744. It is a SUM between the two cases and a PRODUCT inside one of them because this is a counting problem: both cases really happen, across different colourings, and the total of them is wanted rather than the better of them. That is the same distinction count-all-paths-in-a-grid draws against min-cost-path. ways-to-partition-a-set is the other page in this course where one branch contributes several outcomes per shorter arrangement rather than one, and the two coefficients are worth telling apart: there the multiplier is the COLUMN index and changes as you walk right, here it is the constant m - 1 and never moves.THE WHOLE OF ROW 0 IS GIVEN, and so is the whole of column 0. Row 0 is 0, 3, 3, 3. A single tile has 3 colourings, its run is exactly 1 long, so it satisfies at most j for every j from 1 up and none at all at j = 0. Row 0 is given rather than summed for the plainest reason there is: there is no row above it to read. Get its scale wrong and the whole table scales with it, because every cell is linear in row 0. Write 0, 1, 1, 1 there, on the theory that one tile is one way to colour a tile, and the answers come out 26, 76, 222, 648 and 1892 across 4 to 8 tiles, which is exactly one third of the truth at every size. Write 3, 3, 3, 3, letting the impossible column 0 carry a 3 as well, and they come out 81, 234, 684, 1998 and 5832, above the truth at every size. dp[r][0] = 0 for every row past the first. No colouring of a non-empty row of tiles finishes on a run of length at most 0, because it finishes on a run of at least 1. That 0 has to be a BASE rather than a computed cell for two separate reasons and both matter. Arithmetically, dp[r][0] would reach for dp[r - 1][-1], column -1, which is off the table altogether. Editorially, a cell with only one live term would narrate as a bare restatement of another cell, matching none of the arithmetic forms this course narrates in, so it would need writing by hand anyway. And that column is not decorative: every computed cell in column 1 reads it, which is what makes column 1 read 2 × dp[r - 1][3] + 0 and nothing else. The 0 in that sum is a real case that cannot happen, not a term somebody forgot. Getting that edge wrong is quiet at the slider's minimum and loud after it, which is worth measuring rather than guessing at. Copy it across as a 3 and the answers come out 78, 231, 675, 1971 and 5757 across 4 to 8 tiles: EXACTLY RIGHT at 4 tiles and wrong at the other four. Copy it across as a 1 and they come out 78, 229, 669, 1953 and 5703, right at 4 tiles again. The reason is a propagation delay rather than luck: a wrong column-0 cell corrupts column 1 one row down, column 2 the row after that and column 3 only on the third row, so at 4 tiles the damage has not reached the answer cell yet. THE FIRST TERM IS THE SAME CELL FOR EVERY CELL IN THE ROW, which is this table's one genuinely unusual property. dp[r - 1][3] does not merely look like the other cells' first dependency, it IS it: at 8 tiles the whole of row 7 multiplies the same 1944. Recount that by driving the spec at any size and listing each computed cell's first dependency, which is column 3 in every row. Two consequences. A row costs three multiplications by one shared number plus three additions of the neighbour one column to the left, so the multiplication hoists straight out of the inner loop. And the arrows fan out of a single cell rather than tracking each cell they feed, which is what the animation shows. THERE IS NO SKIP AND NOT A SINGLE DOT ON THIS PAGE, unlike the triangular tables in this course that declare a square and skip everything above the diagonal. Every declared cell is live, which the shape makes plain: 4 columns times size rows declared, size + 3 of them given (the 4 in row 0 plus one per row below it in column 0) and 3 times (size - 1) computed, so at the slider's maximum of 8 tiles that is 32 declared, 11 given and 21 computed. Filling row-major, left to right within each row, needs only one ordering property and has it: both cells a computed cell reads sit in the row ABOVE, never in its own row and never below, so the previous row being finished is the whole requirement and the left-to-right order within a row is free.O(n × k) to fill the table, one multiply and one add per cell, so 3 × (n - 1) computed cells at k = 3O(n × k), reducible to O(k) with one rolling row swept right to leftUsing the exact-run state and then forgetting to sum the bottom row. Build dp[r][j] as the final run is EXACTLY j long instead of at most j and you still have a correct table answering a real question, with the same shape, the same row-major fill order and whole numbers throughout. THE ARROWS ARE THE ONE THING THAT DOES CHANGE, and they are the tell. Column 1 has to read the WHOLE of the row above rather than one cell of it, because a fresh run of length 1 can follow any legal colouring whatever run it ended on, and the exact-run state has no single cell holding that total the way this page's column 3 does; columns 2 and 3 then read exactly ONE cell each, since a run of exactly j extends a run of exactly j - 1 and nothing else. Measured, that is a fan of 3, 1, 1 across the row against this page's uniform 2, so the dependency picture is neither two arrows nor even the same count from one cell to the next. The fold this page pushed into its state has to happen somewhere, and in the exact-run table it happens in that column-1 fan. Do not try to keep both halves either. Hold on to this page's two arrows while switching to exact-run bases and what you get is not a different correct answer, it is nonsense: measured down five tiles it reads 0 3 0 0, then 0 0 3 0, then 0 0 0 3, then 0 6 6 6, then 0 12 18 18, against the true exact table's 0 3 0 0, then 0 6 3 0, then 0 18 6 3, then 0 54 18 6, then 0 156 54 18. So the arithmetic is where this mistake hides and the arrows are where you catch it. Now its cost. Read the answer off the bottom right cell of the genuine exact-run table the way this page does, and you have reported the colourings that end in a run of exactly 3 rather than all of them. Measured, that returns 6, 18, 54, 156 and 456 across 4 to 8 tiles against the true 78, 228, 666, 1944 and 5676, which is between 7.6 and 8.2 per cent of the truth at every one of those five sizes. Three things let it survive a glance. FIRST, the wrong answers are a recognisable sequence rather than obvious garbage, and they are not small enough to look broken. SECOND, and worse, every one of them is a number that appears on the CORRECT table: at all five sizes the wrong answer sits in column 1 exactly two rows above the bottom, and it is also exactly twice the true answer three sizes down, so a reader checking it against the picture or against a smaller run can find it and be reassured. THIRD, the sibling mistake of reading column 1 of the exact table is invisible for a stronger reason still: the exact and the cumulative tables AGREE throughout column 1, because at most 1 and exactly 1 are the same thing once column 0 is 0, so that read lands on a genuine cell of this page's own table, the 3888 at 8 tiles. The fix is not to sum more carefully. It is to notice that a fold across a row can be pushed into the state, which is what at most does. The second mistake is which cell the coefficient multiplies, and it is dangerous because it is right at the smallest size the slider offers. Write 2 × dp[r - 1][j] + dp[r - 1][j - 1], reading straight up instead of across to column 3, and the answers come out 78, 216, 576, 1488 and 3744: exactly right at 4 tiles and wrong at the other four. That is why the check behind this page runs every size the slider offers rather than one. The other three index and constant mistakes are loud rather than quiet, and they are worth separating from the quiet ones for exactly that reason. Swap the two terms so the diagonal neighbour is the one multiplied and it reads 57, 147, 369, 891 and 2217, wrong everywhere and below the truth. Drop the coefficient altogether and it reads 21, 39, 72, 132 and 243, wrong everywhere and further below. Use m instead of m - 1 and it reads 189, 747, 2952, 11664 and 46089, wrong everywhere and above. The last mistake is reading the two axes the wrong way round, and this table protects you against it less than the triangular pages do, because every declared cell here is live and a swapped lookup lands on a real number rather than on a dot. It does not always land at all: at 8 tiles the answer is dp[7][3] and the transposed dp[3][7] is off the table, since the table is only 4 columns wide however many rows it has. Inside the top left 4 by 4 corner the swap is silent, though. dp[1][3] is 9 and dp[3][1] is 54, and both are perfectly good counts of something else.
Maximum size square sub-matrix with all 1sdp[r][c] is the side length of the largest square made up entirely of 1s whose BOTTOM-RIGHT CORNER sits exactly at row r, column c. Not the largest square found anywhere so far, and not the largest square that happens to touch this cell somewhere: the corner is pinned here, and the square extends up and to the left from it. dp[r][c] = 3 therefore means a 3 by 3 block of 1s occupies rows r - 2 through r and columns c - 2 through c, and nothing else. Pinning the corner is the entire insight of this page, and reading the cell as 'the biggest square seen up to here' is the misreading that makes the recurrence look impossible, because a biggest-so-far number has no local rule that produces it. Since every cell answers about the square ending at itself, no cell you can name in advance holds the problem's answer; some cell does hold it, but which one depends on the input, so the answer is the largest number the finished table contains.dp[r][c] = 0 where the matrix holds a 0, else 1 + min(dp[r - 1][c], dp[r][c - 1], dp[r - 1][c - 1])Ask how large a square can end at (r, c). If the matrix holds a 0 there, none can, at any size, because every square ending at that corner has to stand on that cell. Otherwise the side is at least 1, and the question becomes what stops it being larger. A square of side k cornered at (r, c) contains three squares of side k - 1 inside it: one cornered at (r - 1, c) directly above, one cornered at (r, c - 1) to the left, and one cornered at (r - 1, c - 1) diagonally up and to the left. All three have to exist, so k - 1 cannot exceed the smallest of those three answers; and going the other way, if all three do reach k - 1 then together with this cell's own 1 they tile the whole k by k block with nothing left uncovered. So the side is one more than the SMALLEST of the three. This is the first three-way comparison in the course, and the third candidate is not decoration: the diagonal neighbour is the only one of the three that can see a hole sitting up and to the left, and dropping it is the standard way to get this problem wrong.Row 0 and column 0 are declared rather than derived, for a reason that is the recurrence in miniature: a square cornered in the top row has no room above it and a square cornered in the left column has no room to its left, so neither can have a side above 1. Each of those cells is simply a copy of the input, 1 where the matrix holds a 1 and 0 where it holds a 0. Every interior cell is computed, including the ones standing on a 0: those resolve to 0 without consulting a single neighbour, because no neighbour value could rescue a square that has to stand on a 0, so nothing is read and there is nothing to predict. Filling row-major, left to right within each row, means all three neighbours are final before they are used, which is the only ordering property the recurrence needs, and notice that the diagonal one costs nothing extra: a row-major sweep finished the whole previous row before it started this one.O(rows × cols)O(rows × cols), reducible to O(cols) with one rolling row plus one saved diagonalReading a cell as 'the largest square anywhere in the board so far'. It is not: it is the largest square whose bottom-right corner is exactly that cell, which is why a cell can fall back to 0 or 1 while a far bigger square already sits elsewhere in the table, and why the answer has to be a scan over the whole table rather than the value in the last cell. Here the answer 4 sits at row 6, column 7 while the last cell holds 1. The second trap is taking the min of only two neighbours, above and left, and forgetting the diagonal, and it is not a matter of style: at row 1, column 2 of this matrix the cell above holds 1 and the cell to the left holds 1, so a two-way min writes 2 and claims a 2 by 2 block of 1s at rows 0 to 1, columns 1 to 2. There is a 0 at row 0, column 1. The diagonal neighbour, holding 0, is the only one of the three that sees it, and it is what pulls the cell back to the correct 1.
Maximum Tip Calculatordp[i][j] is the largest total tip collectable from the FIRST i customers when exactly j of them went to the first waiter. Two numbers name the state, how many customers have been served and how many of those the first waiter took, so the table is a grid even though the two inputs are flat lists. READ THE COLUMN CAREFULLY, because this is the one thing on this page a reader can misread into being unable to read the table at all. THE COLUMN COUNTS ONE WAITER, NOT BOTH. j is the FIRST waiter's share, and the second waiter's share is i - j, which appears nowhere on the screen. Row 6, column 2, which at 9 customers holds 31, is not two customers, it is six: two served by the first waiter and four by the second, and that four is the number you have to subtract to see. WHY THE SUBTRACTION IS LEGAL is worth separating into its two halves, because they come from different parts of the statement and only one of them is about the caps. The half that makes i - j the second waiter's count is simply that EVERY one of the first i customers is served by somebody, so the two waiters' counts add to i; nothing about x or y is needed for that, and it survives making the caps slack. The half that x + y = n buys is narrower and just as useful: with the caps exactly partitioning the customers, row n admits only the split x and y, so the bottom row holds exactly ONE live cell and the answer is a cell rather than a fold across a row. The first two variations below take those two halves away one at a time and they do different damage. The cell holds a TOTAL and nothing about who served whom; if you want the assignment itself you walk the table backwards afterwards, and at 9 customers that walk hands customers 3, 4, 5, 7 and 9 to the first waiter. One more thing the cell does not hold: dp[i][j] is not the best you can do with j slots and i customers to choose from, it is the best you can do having served ALL i of them. Every customer in the first i is assigned to somebody in every arrangement this cell scores, which is why the two arms of the recurrence are a choice between waiters and never a choice to skip.dp[i][j] = max(dp[i - 1][j - 1] + A[i], dp[i - 1][j] + B[i]), where A[i] and B[i] are what customer i tips the first and the second waiterAsk who served CUSTOMER i, the last one in the row. Exactly one of the two waiters did, so the two cases are disjoint and between them exhaustive. If the FIRST waiter served customer i, the other i - 1 customers left j - 1 of them to that waiter, and the tip collected on this step is A[i]: that is dp[i - 1][j - 1], the cell up and to the LEFT, plus A[i]. If the SECOND waiter served customer i, the other i - 1 customers left j to the first waiter, unchanged, and the tip is B[i]: that is dp[i - 1][j] directly ABOVE, plus B[i]. Both sub-problems are the same question on a shorter list, so both are final in the table by the time this cell is filled. It is a max rather than a sum because only one of the two things happened and the better of them is wanted, the same reason min-cost-path takes a min over its two neighbours where count-all-paths-in-a-grid adds them. WHICH TIP GOES WITH WHICH CELL IS THE THING TO GET RIGHT, and the rule is short: the step that moves LEFT is the step that grows the first waiter's count, so it carries the first waiter's tip. The diagonal is the first waiter, the vertical is the second. Note also that every candidate here is ITSELF a sum, a cell plus a tip, which is why a cell with only one live arm still comes out as a two-term addition rather than as a bare copy of its neighbour. Both ends of this table have cells with one arm, at column 0 and on the diagonal, and none of them reads differently from the cells with two. Array indexing is worth a word, because the formula above counts customers from 1 while the snippet indexes the tip arrays from 0: row i is CUSTOMER i, so the tip added on that row is a[i - 1], not a[i].THERE IS EXACTLY ONE BASE, dp[0][0] = 0. No customers served, nobody assigned to either waiter, no tips collected. Every other live cell is computed. TWO SEPARATE SKIP CONDITIONS CARVE THE SHAPE, and both are IMPOSSIBILITY rather than zero. A position with col > row would have the first waiter holding more customers than have been served between the two of them, which is not a hard arrangement to score, it is not an arrangement. A position with row - col > y would leave the second waiter more than their cap of y customers, which the statement forbids outright. A zero in either place would look like a legal assignment worth nothing, when what is actually there is no assignment at all. Nothing is ever written to either kind of position and no live cell reads one, which is why a cell in COLUMN 0 or on the DIAGONAL has ONE genuine candidate rather than one real candidate plus one convenient zero. Those two positions are where an arm is missing, and they are NOT the same thing as the two ends of a row: a row's ends are max(0, i - y) and min(i, x), which coincide with column 0 and the diagonal only while i is at most y. Measured at 9 customers: of the seventeen row-end cells that get computed, nine have one arm and eight have two. Row 5 starts at column 1 rather than column 0, so its left end dp[5][1] = 26 is a row end with both arms, reading dp[4][0] = 15 and dp[4][1] = 22; and dp[9][5] = 56 is both ends of row 9 at once and still reads dp[8][4] = 47 and dp[8][5] = 49. At 4 customers it is four one-armed against three two-armed. Where each condition bites: col > row shaves the TOP RIGHT, so row i stops at column min(i, x); row - col > y shaves the BOTTOM LEFT, so row i starts at column max(0, i - y). What is left is in exact bijection with the pairs of counts inside the two caps, one live cell per (first waiter's count, second waiter's count) with both inside their own cap, so the live cells number (x + 1) times (y + 1) inside the (n + 1) times (x + 1) rectangle the grid declares. Recount that by driving the spec at a slider size and counting its writes against the declared shape rather than trusting the formula. THE DOTS ARE NOT ZEROS, and this page is unusual in how much it would GET AWAY WITH if they were, which is worth measuring rather than borrowing another page's alarm. min-sum-in-a-triangle takes a min over sums, so a zero above its diagonal is a free cheap number the descent gladly cheats through and its answer comes out below the truth. This page takes a MAX over sums of positive tips, so a zero is the least attractive thing a phantom position could offer: it can only win where the honest candidate is smaller, and every honest candidate here is a non-negative running total plus a positive tip. Measured at every one of the six slider sizes: fill the whole rectangle with no skip at all, let each phantom position compute from its neighbours, and the answer cell is unchanged, 26, 34, 35, 43, 47 and 56. The only phantom positions a live cell could even read are the ones just above the diagonal, at (i - 1, i), read by the diagonal cell (i, i); the bottom left region is unreachable outright, because a position at (r, c) with r - c > y is read only by (r + 1, c) and (r + 1, c + 1), and both of those are further past the same cap. NOW STATE THE MARGIN, because the margin is what makes that a fact about these numbers rather than a guarantee. At 9 customers the diagonal cells dp[1][1], dp[2][2], dp[3][3], dp[4][4] and dp[5][5] beat their phantom zero by 2, 9, 15, 26 and 32. The 2 at dp[1][1] is the whole of the safety margin at the top of the table, and it is exactly A[1] - B[1], nine against seven; had customer 1 tipped the second waiter more than the first, the phantom zero would have won that cell outright. So the correctness of this table rests on the position not existing, NOT on the operator being forgiving. Filling row-major, left to right within each row, needs one ordering property and has it: both cells a computed cell reads sit in the row ABOVE, never in its own row, so the previous row being finished is the whole requirement and the order within a row is free.O(n × x), one max of at most two candidates per live cell, with (x + 1) × (y + 1) live cells inside the (n + 1) × (x + 1) rectangle the grid declaresO(n × x), reducible to O(x) with one rolling row swept right to leftGiving each customer to whoever tips them more, TIES TO THE FIRST WAITER, then handing the rest to the other waiter once one of them hits their cap. It is the natural first instinct and it loses at every size this page can show: 17 against 26, 25 against 34, 26 against 35, 34 against 43, 38 against 47 and 44 against 56, a gap of 9 at the first five sizes and 12 at the sixth. The tie-break has to be named rather than assumed, because customer 2 is worth exactly 2 to each waiter, so "whoever tips them more" decides nothing there and those six figures are the tie-to-first reading. Send the tie to the second waiter instead and every figure moves, to 21, 32, 33, 38, 42 and 45; the rule still loses at all six sizes, which is the part this page rests on. WHY IT LOSES IS NOT THAT GREED IS WRONG HERE, and getting that right is worth more than the pitfall itself. A greedy rule does solve this problem exactly, as the execution note works out; the instinct just reaches for the WRONG greedy rule. Asking whether A[i] beats B[i] uses only the SIGN of the margin A[i] - B[i] and throws away its SIZE, and the sizes are what matter, because the first waiter's slots are scarce and should go to the customers the first waiter is worth the most EXTRA on. At 9 customers the margins run 2, 0, 4, 7, 4, 1, 7, -3, 7 across customers 1 to 9. The five largest sit at customers 3, 4, 5, 7 and 9, which is exactly the set the table picks. The sign rule instead walks left to right and fills the cap with customers 1 to 5, two of whom, customers 1 and 2, have margins of 2 and 0; by the time customers 7 and 9 arrive, each worth 7 more to the first waiter, there is no slot left. That is 36 on the first waiter and 8 on the second for 44, against the table's 42 and 14 for 56. The two assignments differ by a single two-for-two swap: move customers 1 and 2 out of the first waiter's five and customers 7 and 9 in, and that side gains 6; the second waiter then takes 1 and 2 in place of 7 and 9 and gains 6 as well. Twice 6 is the whole of the 12. THE SECOND TRAP IS READING THE COLUMN AS A CUSTOMER COUNT. It is one WAITER'S customer count. dp[6][2] = 31 at 9 customers is six customers served, two by the first waiter and four by the second; it is not two customers, and it is not four either. Every cell's value totals the tips of all i customers in its row, never just the j in its column. The third trap is treating the shape as the lower triangle those other 2D pages use. It is a triangle shaved at BOTH ends, one condition per end, and the two ends are shaved for different reasons: col > row is arithmetic nonsense, more customers with one waiter than have been served at all, while row - col > y is a real assignment that a stated cap forbids. Only the second one moves as the caps move. This shape does protect you against the classic index swap better than a full rectangle would, and the reason is exact rather than lucky: a live cell off the diagonal has col < row, so the swapped position has col > row and is never a live cell, at any slider size. WHERE it lands depends on the row rather than on the slider: while the row is at most x the swap is a dot inside the table, and past that the column index runs off the declared width altogether. Measured at 9 customers, of the 24 live off-diagonal cells 14 transpose to a dot and 10, every one of them in rows 6 to 9, transpose off a table that has only 6 columns; at 4 customers it is 3 and 3. The answer cell dp[9][5] is one of those ten, its transpose dp[5][9] landing outside the declared columns, but nothing there is special to the answer cell.
The painter's partition problemdp[p - 1][i - 1] is the least time the SLOWEST of p painters can be held to when the first i boards, in the order they are given, are shared out among them and every painter takes one contiguous, unsplit block. Two numbers name the state, how many painters and how many boards, so the table is a grid rather than a row. The cell holds one time and nothing about where the cuts fell; if you want the blocks themselves you walk the table backwards afterwards. Row index is p - 1 and column index is i - 1, so the bottom right cell is the whole crew on the whole job, and every cell above or to the left of it is a smaller crew, a shorter job, or both. Watch the indices, because this page uses more than one convention. The recurrence below is written the way it reads best, dp[p][i] for p painters over the first i boards, so each of its indices sits one place later than the grid coordinate it points at: the dp[3][10] of the recurrence is the cell the grid labels dp[2][9]. Read every dp[...] here against the words next to it. The recurrence and the derivation under it count painters and boards from 1; every reference to a cell of the grid itself, in this panel and in the step-by-step narration and the gates, uses the zero-based row and column. The code further down is a THIRD labelling, on purpose, because it is written the way you would actually type it: painters zero-based like the grid, boards one-based over a padding column like neither. Its dp[k - 1][n], the grid's dp[2][9] and the recurrence's dp[3][10] are all the same cell, and its dp[p][i] is p + 1 painters where the recurrence's dp[p][i] is p, which is worth holding onto because the highlighted line of code and the narrated cell move together.dp[p][i] = min over j of max(dp[p - 1][j], load(j + 1, i)), for p painters over the first i boardsAsk where the LAST painter's block BEGINS. If it begins after board j, that painter's time is the load of boards j + 1 through i, a number you add up from the board lengths and from nothing else. The other p - 1 painters then have to cover the first j boards as evenly as they can, which is the same question on a shorter row and a smaller crew, so dp[p - 1][j] already answers it. A crew waits for its slowest member, so this cut costs the crew the LARGER of those two numbers. Every j from p - 1 up to i - 1 is a legal place for that block to begin, exactly one of them is what the best assignment did, and you want the j that makes the larger of the two numbers as small as it can be. Hence a min over maxes. Notice what the max is doing, because it is the part that reads oddly at first: it is not comparing two ways of arriving here, the way min-cost-path compares a step down against a step right. It scores ONE cut by its own worst block. The min is what compares the cuts.Row 0 is one painter, and one painter has no decision to make: p = 1 means every board from 1 to i is theirs, so dp[0][i - 1] is the plain total of the first i boards. That is why the whole of row 0 is a base rather than a computed cell, and why it reads as a running total, 10, 20, 25, 29 and on up to 61 at ten boards. It is also the only row that needs no comparison, which makes it the right place for a sweep to start. THE DOTS ARE NOT ZEROS, and it is worth being exact about what that does and does not buy you here. A grid has to be a rectangle, so the table is declared 3 rows by however many boards, but a cell with i < p asks three painters to share two boards while every painter must paint at least one contiguous block. That has no answer rather than a cheap one, so those positions render as a dot because they DO NOT EXIST. Nothing is written to them and no cell depends on one: the split range, j from p - 1 up to i - 1, is exactly the range that stays clear of them. Now the part that is easy to get wrong in the other direction, and this page is the exception rather than the rule. On a min-over-SUM table, a zero sitting where a dot belongs is a free move and the min takes it every time: min-sum-in-a-triangle says exactly that about its own dots, and it is right to, because a descent that could stand on the empty half of that table would come back under the truth. It does not transfer here. There is exactly one place the mistake could even bite, since row 0 has no dots at all: widen the range and row 2's leftmost candidate becomes the dot at dp[1][0], which the shipped range never reads. So fill every dot with a zero AND widen the split range to allow every j, and all seven answers come out unchanged, 10, 11, 14, 15, 19, 20 and 25, with not one live cell moving either. The max is what protects this table: a zero prior gets paired with the LARGEST remaining block, so max(0, that block) is just the block. At dp[1][0] that block is boards 2 through 10, a SUFFIX and not one of row 0's prefix totals, and it comes to 51 against a true 25 at ten boards, so the min never takes it. So the min over maxes makes the ragged half harmless, which is a property worth noticing rather than a hazard to fear, and the reason to keep the dots is that a position which does not exist is not a value, not that a zero would hand you the wrong number. The fill order needs one property and has it. Every candidate a cell reads lies in the row ABOVE and at a column strictly to the LEFT of this one, never in this row and never further right, so a row-major sweep left to right within each row has all of them final before the cell asks for them.O(k × n^2) for k painters and n boards, so 3 × n^2 hereO(k × n), reducible to O(n) with one rolling rowSplitting by COUNT instead of by LOAD. Three painters, so give each a third of the boards: it is the first thing everyone tries, and on ONE natural way of pinning down what a third of the boards means it loses at every one of the seven slider sizes. Cutting into three blocks as equal in count as possible, with any remainder going to the earliest blocks, loses by 10, 9, 6, 10, 6, 5 and 4 from 4 boards up to 10. At 7 boards that rule puts boards 1, 2 and 3 together, 10 and 10 and 5, for 25, against a true optimum of 15. Be careful how much you conclude from that, because the remainder convention is doing some of the work. Send the remainder to the LATEST blocks instead and the same rule ties the optimum at 4 boards; take the best of EVERY cut whose three counts differ by at most one and it ties the optimum at 4 boards and at 10, while still losing by 4, 6, 5, 1 and 5 at the other five sizes. So counting boards is not a good rule with a bad tie-break. It is a rule with no reason to be right that sometimes gets lucky, and the reason is right there in the input: a board's count is always 1, while its length here runs from 2 to 10. The second trap is assuming the heaviest block sits somewhere predictable, usually that the last painter is left the smallest one. It does not. Following the cuts this table picks, the heaviest block is the third at 5, 6 and 10 boards, the second at 7 and 8, the first at 9, and tied across the first two at 4. At the slider's top the last painter carries 25 against 20 and 16. Worse for anyone hoping to name THE optimal cut: at 10 boards four different cuts all score 25, and two of them do leave the last painter the smallest block, so which cut you get is a property of your tie-break and not of the input. The third trap is reading the answer off the wrong axis. Row 2 is three painters and the last column is all the boards, and the answer is the cell where both are true. Further left in row 2 is three painters on a SHORTER job, which answers a different question and is never a LARGER number: strictly smaller from 5 boards up, and at 4 boards the single cell to the left of the answer holds 10 exactly as the answer does. Higher up in the last column is FEWER painters on this job, which is strictly larger at every slider size.

Remaining Problems

These problems are catalogued but not yet broken down, so they are listed here as plain text rather than links.

Basic Problems

  • nth Catalan Number
  • Count Unique BSTs
  • Count Valid Parenthesis
  • Ways to Triangulate a Polygon
  • Pascal's Triangle

Easy Problems

  • Subset Sum Problem
  • Coin change problem – Count Ways
  • Cutting a Rod
  • Longest Common Substring

Medium Problems

  • Water Overflow
  • Longest Common Subsequence
  • Edit Distance
  • 0-1 Knapsack Problem
  • Printing Items in 0/1 Knapsack
  • Unbounded Knapsack
  • Partition Problem
  • Longest Palindromic Subsequence
  • Longest Common Increasing Subsequence
  • All distinct subset (or subsequence) sums
  • Minimum insertions for palindrome
  • Wildcard Pattern Matching
  • Regular Expression Matching
  • Arrange Balls with adjacent of different types
  • Bellman–Ford Algorithm
  • Floyd Warshall Algorithm

Hard Problems

  • Largest X Bordered Square
  • Egg Dropping Problem
  • Palindrome Partitioning
  • Palindromic Substring Count
  • Optimal Strategy for a Game
  • Matrix Chain Multiplication
  • Printing Matrix Chain Multiplication
  • Maximum sum rectangle
  • Stock Buy and Sell – At-Most k Times
  • Stock Buy and Sell – At Most 2 Times
  • Min cost to sort strings using Reversals
  • Count of AP Subsequences

DP on Trees

  • Max Height of Tree when any Node can be Root
  • Longest repeating and non-overlapping substring

Advanced Concepts

  • Bitmasking and DP
  • Traveling Salesman Problem
  • Digit DP
  • Sum over Subsets