Edit Distance & Sliding Window
Overview
Session 8 opened with two spaced-repetition reviews — Coin Change and Unique Paths II — before finishing the Edit Distance implementation deferred from session 7. The session closed with an introduction to the Sliding Window pattern, culminating in the monotonic deque technique for O(n) sliding window maximum.
Concept
Edit Distance
editDistance(s, t) is the minimum number of insert / delete / replace operations to transform s into t. The DP table is (s.length+1) × (t.length+1), where dp[i][j] = edit distance between s[0..i) and t[0..j).
Base cases:
dp[0][j] = j— j insertions to buildt[0..j)from emptydp[i][0] = i— i deletions to reduces[0..i)to empty
Recurrence:
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] # free diagonal (match)
else:
dp[i][j] = min(
dp[i-1][j-1], # replace
dp[i-1][j], # delete from s
dp[i][j-1], # insert into s
) + 1
Each of the three neighbors maps to one operation. Time: O(m·n), Space: O(m·n).
Sliding Window
Two flavors:
- Fixed size — window of size
kslides forward; drop left element when size exceedsk. - Variable size — expand
runtil constraint breaks, shrinkluntil it holds again.
Both are O(n): each element enters and exits the window at most once.
Monotonic deque enables O(n) sliding window maximum. The deque stores indices (not values) in decreasing order of nums[index]. Two invariants:
- Pop from back any index whose value is ≤
nums[r]before pushingr— those elements are beaten bynums[r]and expire sooner. - Pop from front any index that has left the window (
index < r - k + 1).
Front of deque is always the current window maximum.
Implementation
Edit Distance
editDistance(s: string, t: string): int {
dp = new Array[][](s.length + 1, t.length + 1)
dp[0][j] = j for j in [0..t.length]
dp[i][0] = i for i in [0..s.length]
for (i = 1; i <= s.length; ++i) {
for (j = 1; j <= t.length; ++j) {
fromDiagonal = s[i-1] == t[j-1] ? dp[i-1][j-1] : dp[i-1][j-1] + 1
fromAbove = dp[i-1][j] + 1
fromLeft = dp[i][j-1] + 1
dp[i][j] = min(fromDiagonal, fromAbove, fromLeft)
}
}
dp.last.last
}
Key bugs caught: base-case loops used s.length/t.length with swapped axes, and used i+1 instead of i as the value — overwriting dp[0][0] = 0 with 1. The rule: the index is the value when seeding a running-count base case.
Sliding Window Maximum
slidingWindowMax(nums: int[], k: int): int[] {
if (k < 1 || nums.empty || k > nums.length) return []
deque: int[] = [] // stores indices; value = nums[index]
result: int[] = []
for (r = 0; r < nums.length; ++r) {
// drop back elements that can never be max
while (deque.at(-1) != null && nums[deque.at(-1)] <= nums[r])
deque.pop()
deque.push(r)
// drop front elements outside the window
while (deque[0] < r - k + 1)
deque.shift()
if (r >= k - 1) result.push(nums[deque[0]])
}
return result
}
Key bugs caught: operator precedence — ?? has lower precedence than <, so deque.at(-1)?.value ?? Infinity < nums[r] parses as deque.at(-1)?.value ?? (Infinity < nums[r]). Always parenthesize: (deque.at(-1)?.value ?? Infinity) < nums[r]. Also: store indices only; value is always recoverable as nums[index].
Problems
Coin Change (spaced rep)
Return minimum coins to make amount, or -1.
Approach: 1D DP. dp[i] = min coins to make i. For each amount, map each coin to dp[i - c] (null if out of range or unreachable), filter nulls, take min + 1.
Solution: Clean from memory. Using null instead of Infinity elegantly sidesteps the Infinity + 1 sentinel issue. The null filter handles both range and reachability in one pass.
Unique Paths II (spaced rep)
Grid with obstacles (1 = blocked). Count paths from top-left to bottom-right moving only right or down.
Approach: In-place 2D DP. Obstacle cells → null. Otherwise sum reachable neighbors.
Bug repeated from session 7: Missing seed for grid[0][0] = 1. When both neighbors are null, the origin is incorrectly marked unreachable. Fix: if (i === 0 && j === 0) { grid[0][0] = 1; next }.
Edit Distance
Transform string s into string t with minimum insert/delete/replace operations.
Approach: 2D DP as described above.
Bugs: Base-case row/col used swapped bounds (s.length vs t.length) and i+1 values starting from i=0, overwriting the dp[0][0] = 0 seed.
Sliding Window Maximum
Given nums and window size k, return max of each window.
Approach: Monotonic decreasing deque of indices. O(n) time, O(k) space.
Key insight: Any element smaller than the current nums[r] and to its left can never be a future maximum — it’s dominated and expires sooner. Discard it immediately from the back.
Session transcript
Spaced rep: Coin Change
James implemented cleanly from memory. Used null instead of Infinity — the .filter(x => x != null) does double duty (range check and reachability check). No bugs. Time O(coins × amount), Space O(amount).
Spaced rep: Unique Paths II
James implemented in-place DP with null-as-unreachable. Same seed bug as session 7: grid[0][0] has both neighbors null, so it gets marked null instead of 1. Fix: special-case the origin before the main loop.
Edit Distance
James derived the recurrence correctly (match = free diagonal, mismatch = min of three neighbors + 1). Implementation bugs in base-case seeding: loops used s.length/t.length with swapped axes, and i+1 values starting from 0 — overwriting dp[0][0]=0 with 1. Core loop was correct.
Sliding Window — concept
Introduced fixed vs variable window, O(n) via single-pass. James correctly identified that when the previous max falls off the left edge you need to know the next candidate — this is the core problem. Noted O(n log k) with a capped heap; asked how to get O(n). Correctly ruled out HashMap.
Introduced monotonic deque. James understood the “never be future max” insight immediately. Connected the insight to two maintenance rules: back-pop on new element, front-pop on expiry.
Sliding Window Maximum implementation
James’s implementation was structurally correct. Two bugs: (1) operator precedence — ?? lower than <, causing the while condition to misbehave; (2) result undeclared. Used {index, value} objects in the deque; noted that only indices are needed since value = nums[index].
Pause to deepen intuition: James asked how “elements dominated by nums[r] can never be max” translates to the two rules. Explained deque as “candidates for future maximums,” each surviving two filters: still in window, still potentially max. Connected front = current max, back = most recent survivor.