<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Algorithm design
All lessons in this course

Lesson 43 of 50 15 minAdvanced

Dynamic programming: grids and sequences

Apply DP to two-dimensional grids (counting paths around obstacles, cheapest route) and to sequences (longest increasing subsequence, maximum subarray with Kadane’s algorithm).

The two most common shapes of DP problems are grids, where a cell’s answer comes from its neighbours, and sequences, where the answer for position i comes from earlier positions. Once you recognise the shape, the recipe from the last lesson does the rest.

Counting paths on a grid

A robot (or a cable) goes from the top-left corner of a site plan to the bottom-right, moving only right or down, and some cells are blocked. How many different routes are there? The last move into a cell came either from above or from the left, so paths[r, c] = paths[r − 1, c] + paths[r, c − 1], and a blocked cell has 0.

Interactive

DP on a grid

Move only right or down from the top-left to the bottom-right. A cell’s count = count from above + count from the left. Click cells to block or unblock them.

Start in the top-left corner: there is exactly 1 way to be there.
GridPaths.cs
string[] site ={    "......",    "..#...",    "...#..",    "......",    "..#..."};Console.WriteLine(CountPaths(site)); static long CountPaths(string[] g){    int rows = g.Length, cols = g[0].Length;    var paths = new long[rows, cols];    for (int r = 0; r < rows; r++)        for (int c = 0; c < cols; c++)        {            if (g[r][c] == '#') continue;                        // blocked: stays 0            if (r == 0 && c == 0) { paths[r, c] = 1; continue; }            long fromAbove = r > 0 ? paths[r - 1, c] : 0;            long fromLeft = c > 0 ? paths[r, c - 1] : 0;            paths[r, c] = fromAbove + fromLeft;        }    return paths[rows - 1, cols - 1];}
Output
30

The cheapest route

Same grid shape, different question: each cell has a cost, and we want the cheapest right/down route. Replace “+” with “min”: best[r, c] = cost[r, c] + min(best above, best left). To recover the route itself, walk back from the corner, always to the cheaper neighbour.

MinPathSum.cs
int[,] cost ={    { 1, 3, 1, 2, 8, 1 },    { 1, 5, 1, 9, 1, 2 },    { 4, 2, 1, 3, 2, 6 },    { 7, 1, 8, 1, 1, 2 },    { 2, 4, 1, 5, 3, 1 }};Console.WriteLine(MinPathSum(cost)); static int MinPathSum(int[,] cost){    int rows = cost.GetLength(0), cols = cost.GetLength(1);    var best = new int[cols];                       // one row is enough    for (int r = 0; r < rows; r++)        for (int c = 0; c < cols; c++)        {            if (r == 0 && c == 0) best[c] = cost[0, 0];            else if (r == 0) best[c] = best[c - 1] + cost[r, c];          // only from the left            else if (c == 0) best[c] = best[c] + cost[r, c];              // only from above            else best[c] = Math.Min(best[c], best[c - 1]) + cost[r, c];   // above vs left        }    return best[cols - 1];}
Output
15

Notice the memory trick: best[c] still holds the value from the row above until it is overwritten, so one array of length cols replaces the whole table. Time O(rows × cols), memory O(cols).

Longest increasing subsequence

Monthly readings { 3, 10, 2, 1, 20, 4, 6, 7 }. What is the longest run of readings that keeps increasing, if you may skip some (a subsequence, not necessarily consecutive)? State: lis[i] = length of the longest increasing subsequence that ends at i. Recurrence: look at every earlier j with a smaller value and extend the best one.

Lis.cs
int[] readings = { 3, 10, 2, 1, 20, 4, 6, 7 };Console.WriteLine(Lis(readings));Console.WriteLine(LisFast(readings)); static int Lis(int[] a)                                   // O(n²){    var lis = new int[a.Length];    for (int i = 0; i < a.Length; i++)    {        lis[i] = 1;                                       // just a[i] on its own        for (int j = 0; j < i; j++)            if (a[j] < a[i]) lis[i] = Math.Max(lis[i], lis[j] + 1);    }    return a.Length == 0 ? 0 : lis.Max();} static int LisFast(int[] a)                               // O(n log n){    var tails = new List<int>();     // tails[k] = smallest end of an increasing run of length k + 1    foreach (int x in a)    {        int pos = tails.BinarySearch(x);        if (pos < 0) pos = ~pos;                          // where x would be inserted        if (pos == tails.Count) tails.Add(x);             // extends the longest run        else tails[pos] = x;                              // a smaller end for that length    }    return tails.Count;}
Output
4
4

The O(n log n) version keeps, for every length, the smallest value an increasing run of that length can end with, and uses binary search (with the ~index trick from the searching lesson) to update it. It is a good example of DP and binary search working together.

Sequence DP: the pattern

ProblemStateRecurrence
Stairsways to reach iways[i−1] + ways[i−2]
Non-adjacent picksbest up to imax(best[i−1], best[i−2] + v[i])
Longest increasing subsequencelongest ending at i1 + max(lis[j]) for j < i with a[j] < a[i]
Maximum subarray (Kadane)best sum ending at imax(a[i], end[i−1] + a[i])
Quick check

In the grid-paths problem, why does each cell only look at the cell above and the cell to the left?

Quick check

For { 5, 1, 6, 2, 7, 3, 8 }, what is the length of the longest increasing subsequence?

Practice

Implement Kadane’s algorithm: the largest sum of a run of consecutive values, in O(n) time and O(1) memory. { -2, 1, -3, 4, -1, 2, 1, -5, 4 } → 6 (4 − 1 + 2 + 1); the profit data from the divide-and-conquer lesson { 3, -8, 5, -1, 6, -9, 4, 2, -3 } → 10.

Show a hint

State: the best sum of a run that ends at the current position. Either extend the previous run or start fresh here: endingHere = max(x, endingHere + x). Track the largest value it ever takes.

Show the solution
Solution.cs
Console.WriteLine(Kadane(new[] { -2, 1, -3, 4, -1, 2, 1, -5, 4 }));Console.WriteLine(Kadane(new[] { 3, -8, 5, -1, 6, -9, 4, 2, -3 })); static int Kadane(int[] a){    int endingHere = a[0], best = a[0];    for (int i = 1; i < a.Length; i++)    {        endingHere = Math.Max(a[i], endingHere + a[i]);   // start fresh or extend        best = Math.Max(best, endingHere);    }    return best;}
Output
6
10

Remember

  • Grid DP: a cell’s answer comes from the neighbours its last move came from.
  • Counting uses “+”, optimising uses “min” or “max”; walk back from the end to recover the route.
  • Rolling arrays cut memory from O(rows × cols) to O(cols).
  • Sequence DP: define the state as “the best ending at i”; LIS and Kadane are the classic examples.
0 of 50 lessons completed