</> MAANG.io
coding interview · 301

Advanced

Advanced coding interview preparation covering complex algorithms, system design, and optimization techniques for senior-level positions.

0/95 solved 0% complete

Grid and Position DP

What is this?

Each of these indexes by a position and needs exactly one extra coordinate to close the recurrence. A grid cell needs nothing more — the strictly-increasing rule makes the graph acyclic on its own. Typing needs to know where the other finger is. Shelving needs to know where the current row started. Scheduling needs to know the last job that finished in time, which is a binary search rather than a loop.

flowchart TD A["state = position + ?"] --> B["Longest Increasing Path
nothing — strict increase = acyclic"] A --> C["Two Fingers
+ the other finger's position"] A --> D["Bookcase Shelves
+ where this shelf begins"] A --> E["Job Scheduling
+ last compatible job (binary search)"] B --> B1["memoise on the cell"] C --> C1["only ONE finger is at word[i−1]"] D --> D1["walk backwards until width is exceeded"] E --> E1["sort by end, bisect on start"]

💡 Fun fact: Minimum Distance to Type a Word Using Two Fingers looks like it needs a state of (index, finger1, finger2) — 27 × 27 positions at every index. It does not. After typing word[i−1], one of the fingers is necessarily on word[i−1], which is already known from the index. So only the other finger's position needs storing, and the state collapses to (index, other). Recognising that part of a state is implied by the rest is one of the most reliable ways to turn a DP that will not fit into one that does.

🔓 The 4 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


The one-line idea: write down the smallest thing you must remember besides the position. Anything the index already implies is not state — and removing it is usually the difference between passing and timing out.


1. When the rule makes it acyclic

@lru_cache(None)
def go(r, c):
    best = 1
    for nr, nc in neighbours(r, c):
        if mat[nr][nc] > mat[r][c]:            # strictly greater → no cycles possible
            best = max(best, 1 + go(nr, nc))
    return best
return max(go(r, c) for r in range(m) for c in range(n))

No visited set is needed, and that is the point worth saying aloud: because every step strictly increases, a path can never return to a cell it has left. The memo makes each cell computed once, giving O(mn).

The alternative is a topological sort by value, which is the same computation arranged differently and useful to mention.

2. When part of the state is implied

@lru_cache(None)
def go(i, other):                              # other = the finger NOT on word[i-1]
    if i == len(word): return 0
    prev = word[i-1] if i else None
    return min(dist(prev,  word[i]) + go(i+1, other),   # same finger continues
               dist(other, word[i]) + go(i+1, prev))    # the other finger moves

Two choices per character, and None — no finger placed yet — costs zero to move from, which handles the first two characters without a special case. Distance is Manhattan on the 6-wide keyboard grid: divmod(ord(ch) - ord('A'), 6).

3. When the choice is where the group begins

for i in range(1, n + 1):
    w = h = 0
    for j in range(i, 0, -1):                  # books j..i share the last shelf
        w += books[j-1][0]
        if w > shelf_width: break              # cannot widen further
        h = max(h, books[j-1][1])
        dp[i] = min(dp[i], dp[j-1] + h)

Walking backwards from i is what lets the width accumulate incrementally and the break fire as soon as the shelf is full. Books must stay in order, which is why "which books share the last shelf" is always a contiguous suffix — and that is the fact that makes the whole DP work.

4. When the transition is a binary search

jobs = sorted(zip(end, start, profit))
ends, dp = [0], [0]                            # both strictly increasing
for e, s, p in jobs:
    i = bisect_right(ends, s) - 1              # last job finishing at or before s
    if dp[i] + p > dp[-1]:
        ends.append(e); dp.append(dp[i] + p)
return dp[-1]

Sorting by end time is what makes ends increasing and therefore searchable. dp is kept monotonic by appending only when the new total beats the current best, which means dp[-1] is always the answer so far and no final scan is needed.


5. A 30-second worked example (two fingers)

word = "CAKE" on a 6-wide keyboard: A is (0,0), C is (0,2), E is (0,4), K is (1,4).
Distances: C→A = 2, C→K = 3, A→K = 5, K→E = 1.

the greedy — always make the cheapest move now:
  C   place a finger there                                  0
  A   same finger C→A = 2   vs   place the other finger = 0  → 0
  K   fingers on C and A:  C→K = 3   vs   A→K = 5            → 3
  E   fingers on K and A:  K→E = 1   vs   A→E = 4            → 1
                                                    total    4

the optimum — split the word between the fingers:
  finger 1 types C then A        C→A = 2
  finger 2 types K then E        K→E = 1
                                                    total    3

The greedy's free move at step 2 is what costs it. Placing the second finger on A looks like a saving of 2 and leaves both fingers stranded on the left of the keyboard, while K and E are both on the right. Paying the 2 to keep a finger free is worth it — and no rule about the current character can see that, which is exactly why the problem is a DP.


6. Where you'll actually meet this

  • Text layout engines. Filling shelves is the same recurrence as breaking a paragraph into lines under a width limit.
  • Revenue management. Weighted job scheduling underlies ad slots, meeting rooms and rental bookings.
  • Terrain and hydrology. Longest increasing path is water flow over a height map.
  • Input and accessibility research. Two-thumb typing cost is a genuine keyboard-layout metric.
  • Resource planning. "Group consecutive items under a capacity limit" appears in shipping, batching and pagination.

7. Problems in this chapter

▶ Longest Increasing Path in a Matrix

Longest strictly increasing path. Memoised DFS — the increase rule makes cycles impossible, so no visited set is needed.
Pattern: memoised DFS on an implicit DAG. Target: O(mn) time and space.

▶ Minimum Distance to Type a Word Using Two Fingers

Least total finger travel. State is (index, other finger) because one finger is always on the previous character.
Pattern: DP with an implied state component. Target: O(n · 27) time.

▶ Filling Bookcase Shelves

Least total height with books kept in order. For each prefix, try every contiguous suffix as the last shelf.
Pattern: prefix DP over contiguous groups. Target: O(n²) time, O(n) space.

▶ Maximum Profit in Job Scheduling

Maximum profit from non-overlapping jobs. Sort by end time and binary-search the last compatible job.
Pattern: DP + binary search. Target: O(n log n) time, O(n) space.


8. Common pitfalls 🚫

  • Adding a visited set to the increasing path. Strict increase already forbids revisiting, and the set breaks the memo.
  • Storing both finger positions. One of them is implied by the index.
  • Walking shelves forward. Backwards accumulation is what lets the width check break early.
  • Forgetting books must stay in order, which is what makes the last shelf a contiguous suffix.
  • Sorting jobs by start time. End time is what makes the binary search possible.
  • Using bisect_left on the ends array, which admits a job that ends exactly when the next begins — or excludes one that should qualify. Check the boundary against the problem's definition.
  • Memoising on mutable state. Keys must be the position and the small extra coordinate, nothing else.

9. Key takeaways

  1. State = position + the smallest thing you must remember. Anything implied is not state.
  2. A strict-increase rule gives you a DAG for free — no visited set, no cycle handling.
  3. Contiguity is a gift. "Books stay in order" is what makes the shelf a suffix.
  4. Sort to make a transition searchable. By end time, then bisect.
  5. Keeping a DP array monotonic means the answer is always its last element.
  6. None as an initial position removes the special case for the first move.
  7. Why interviewers like it: the naive state is always writable and always too big, so collapsing it is the visible skill.

Order: Longest Increasing Path in a Matrix → Minimum Distance to Type a Word Using Two Fingers → Filling Bookcase Shelves → Maximum Profit in Job Scheduling.

Longest Increasing Path in a Matrix

Core idea: From any cell you may step up, down, left, or right, but only onto a strictly larger value. You want the longest such strictly increasing path anywhere in the grid. The trap is treating this like a maze that needs a visited set and backtracking — but because every legal edge goes from a smaller cell to a strictly larger one, you can never return to a cell you've left (you'd have to step back down). That means the grid, viewed as a graph, is a DAG (directed acyclic graph) — no cycles. And on a DAG the longest path from a node is well defined and reusable, so we memoize: dp[r][c] = length of the longest increasing path that starts at (r,c) = 1 + max(dp of strictly-larger neighbors) (or just 1 if no neighbor is larger). Each cell is solved once and cached, so the whole grid costs O(m·n).


The problem, rephrased

You're given a grid of integers — picture an elevation map where each cell is a height in metres. A hiker stands on some cell and walks to an orthogonally adjacent cell only if it is strictly higher (always climbing, never flat, never descending). Starting from the best possible cell, how many cells does the longest uphill walk visit?

Formally: given an m × n matrix matrix, return the length of the longest path such that consecutive cells are 4-directionally adjacent and strictly increasing in value. The path length counts cells (a single cell is a path of length 1). You may start and end anywhere.

This is LeetCode 329 — Longest Increasing Path in a Matrix.

Input Means Output
[[9,9,4],[6,6,8],[2,1,1]] climb 1 → 2 → 6 → 9 4
[[3,4,5],[3,2,6],[2,2,1]] climb 3 → 4 → 5 → 6 4
[[1]] single cell, nowhere to climb 1

The whole game is realizing the "always climb higher" rule forbids cycles, which is what lets us cache each cell's answer instead of re-exploring it.


Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.