2D Dynamic Programming (LCS)
Overview
Session opened with two spaced-repetition reviews: K Closest Points (heaps) and Walls & Gates (multi-source BFS). Both patterns were recognized correctly. New material introduced 2D DP via Longest Common Subsequence — James derived the recurrence independently and filled the table correctly on the first try. Implementation had three bugs: grid sizing offset, > vs < loop condition, and a ^/** confusion.
Concept
2D DP: extend 1D DP to two dimensions. dp[i][j] represents the answer to a subproblem over two inputs simultaneously — typically two strings or a grid.
LCS recurrence: dp[i][j] = length of LCS of s[0..i-1] and t[0..j-1] (1-indexed into an (s.length+1) × (t.length+1) table with a zero base-case row/column).
if s[i-1] === t[j-1]: dp[i][j] = dp[i-1][j-1] + 1
else: dp[i][j] = max(dp[i-1][j], dp[i][j-1])
Complexity: O(m × n) time and space.
Implementation
James’s implementation with bugs corrected:
function lcs(s: string, t: string): number {
// Grid is (s.length+1) × (t.length+1) to accommodate empty-string base cases.
// Row 0 and col 0 are all zeros — LCS with an empty string is always 0.
const grid: number[][] = Array.from({ length: s.length + 1 }, () =>
new Array(t.length + 1).fill(0)
)
for (let i = 1; i <= s.length; i++) {
for (let j = 1; j <= t.length; j++) {
// Bug fixed: was j > grid[i].length
if (s[i - 1] === t[j - 1]) {
// Bug fixed: index offset from base-case row/col
grid[i][j] = grid[i - 1][j - 1] + 1
} else {
grid[i][j] = Math.max(grid[i - 1][j], grid[i][j - 1])
}
}
}
return grid[s.length][t.length]
}
Bugs caught:
- Grid was
s.lengthrows — needss.length + 1to fit the zero base-case row; same for columns. Comparison inside the loop must uses[i-1]/t[j-1]. - Inner loop used
j > grid[i].length(loop never ran) — should bej <orj <=. s[i]^2uses bitwise XOR, not exponentiation — uses[i]**2ors[i] * s[i].
Problems
K Closest Points to Origin (spaced rep — Heaps)
Return the k closest points to the origin from an array of [x, y] points.
Approach: min-heap ordered by squared distance, extract k times. O(n log n). Efficient variant: max-heap of size k — insert each point, pop when size > k. O(n log k).
Watch out: ^ in JS is bitwise XOR. Use x**2 + y**2 or x*x + y*y for squared distance.
Walls and Gates (spaced rep — BFS/DFS)
Fill each empty room in a grid with its distance to the nearest gate.
Approach: multi-source BFS — seed queue with all gates simultaneously. BFS expands in true distance order, so each room is visited once and gets the correct minimum automatically.
Watch out: Array.at(-1) wraps around. When generating neighbors in a grid, use explicit bounds checking (x >= 0 && x < cols && y >= 0 && y < rows) and index with grid[y][x], not .at().
Longest Common Subsequence
Given strings s and t, return the length of their longest common subsequence.
Approach: 2D DP table. See recurrence and implementation above.
Key insight: the “magical outside realm” framing — when characters don’t match, you discard one with max(...), you don’t add both. The max is what prevents double-counting.
Session transcript
Tutor: This is Session 6. Spaced rep due: K Closest Points (Heaps) and Walls & Gates (BFS/DFS). Then new topic: 2D DP.
K Closest Points: James gave correct approach (min-heap by squared distance, extract k times). Used a.x^2 — flagged as bitwise XOR bug. Correctly identified bounded max-heap variant unprompted. Stated complexity as O(n * k log k) — corrected to O(n log k).
Walls and Gates: James initially asked for clarification on the problem. After seeing the example grid, identified the multi-source challenge: “BFS from each gate, set value to min(existing, distance).” Tutor introduced multi-source BFS — James immediately described it as “all gates are 1 away from a magical outside realm.” Implementation was clean. Bug found: Array.at(-1) wraps around for negative indices; grid neighbors need explicit bounds check with [] indexing.
LCS: James correctly defined dp[i][j] as LCS of s[0..i] and t[0..j]. Worried about double-counting; tutor explained the two-case split. James filled the 4×3 example table correctly on first try and articulated each cell’s recurrence. Implementation had the grid-sizing offset bug (needed s.length+1 rows), the j > direction typo, and the ^ XOR issue from earlier.
James stopped session after LCS, before Unique Paths. Session saved.