- API Kernel:
IntervalDP - Why Interval DP?
- Core Insight
- Universal Template Structure
- Pattern Variants
- Problem Link
- Difficulty
- Tags
- Pattern
- API Kernel
- Problem Summary
- Key Insight
- Template Mapping
- Complexity
- Why This Problem First?
- Common Mistakes
- Related Problems
- Problem Link
- Difficulty
- Tags
- Pattern
- API Kernel
- Problem Summary
- Key Insight
- Template Mapping
- Complexity
- Why This Problem Second?
- Common Mistakes
- Related Problems
- Problem Link
- Difficulty
- Tags
- Pattern
- API Kernel
- Problem Summary
- Key Insight
- Template Mapping
- Complexity
- Why This Problem Third?
- Common Mistakes
- Related Problems
- Problem Link
- Difficulty
- Tags
- Pattern
- API Kernel
- Problem Summary
- Key Insight
- Template Mapping
- Complexity
- Why This Problem Fourth?
- Common Mistakes
- Related Problems
- Problem Comparison
- Pattern Evolution
- Key Differences
- Decision Tree
- Pattern Selection Guide
- Complexity Guide
- Key Indicators for Interval DP
- Universal Templates
- Quick Reference
Core Mechanism: Define
dp[i][j]as the optimal answer for the interval[i, j], then enumerate all possible "split points"kto divide the problem into subproblems.
Interval DP solves problems where:
- The answer depends on a contiguous range/interval
- You need to find the optimal way to process/merge/split the interval
- The order of operations matters (not just which elements)
The key insight is that for any interval [i, j], there exists some "last operation" that splits it:
- Matrix Chain Multiplication: Last multiplication at position
k - Burst Balloons: Last balloon to burst
- Polygon Triangulation: Last triangle to form
By trying all possible last operations and taking the optimal, we build up the solution.
def interval_dp_template(arr: list) -> int:
n = len(arr)
# dp[i][j] = optimal answer for interval [i, j]
dp = [[0] * n for _ in range(n)]
# Base case: single elements or empty intervals
for i in range(n):
dp[i][i] = base_case(i)
# Fill by increasing interval length
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
dp[i][j] = initial_value # inf or -inf
# Try all split points
for k in range(i, j):
candidate = dp[i][k] + dp[k+1][j] + merge_cost(i, k, j)
dp[i][j] = optimal(dp[i][j], candidate)
return dp[0][n-1]| Pattern | Split Point | Merge Cost | Example |
|---|---|---|---|
| Matrix Chain | Where to split multiplication | Product of dimensions | Classic |
| Burst Balloons | Last balloon to burst | nums[i-1]*nums[k]*nums[j+1] |
LC 312 |
| Polygon Triangulation | Third vertex of triangle | v[i]*v[k]*v[j] |
LC 1039 |
| Optimal BST | Root of subtree | Frequency sum | Classic |
https://leetcode.com/problems/burst-balloons/
Hard
- Array
- Dynamic Programming
Interval DP - Last Element Selection
IntervalDP
Given n balloons with values nums[i], bursting balloon i gives coins nums[i-1] * nums[i] * nums[i+1]. After bursting, neighbors become adjacent. Find the maximum coins you can collect.
Instead of thinking "which balloon to burst first", think "which balloon to burst LAST".
If balloon k is the last to burst in interval [i, j]:
- Left side
[i, k-1]and right side[k+1, j]are already burst - Bursting
kgivesnums[i-1] * nums[k] * nums[j+1](boundary values) - Total =
dp[i][k-1] + dp[k+1][j] + nums[i-1]*nums[k]*nums[j+1]
This "reverse thinking" makes the subproblems independent!
def maxCoins(nums: list) -> int:
# Add virtual balloons with value 1 at boundaries
nums = [1] + nums + [1]
n = len(nums)
# dp[i][j] = max coins bursting all balloons in (i, j) exclusive
dp = [[0] * n for _ in range(n)]
# Fill by increasing interval length
for length in range(2, n):
for i in range(n - length):
j = i + length
for k in range(i + 1, j): # k is the last balloon to burst
coins = nums[i] * nums[k] * nums[j]
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + coins)
return dp[0][n - 1]- Time: O(n³) - three nested loops
- Space: O(n²) - 2D DP table
Burst Balloons is the canonical interval DP problem:
- Clear "last operation" intuition
- Classic interval DP structure
- Teaches the "reverse thinking" trick
- Thinking forward - Bursting first makes subproblems dependent
- Wrong boundary handling - Add virtual balloons with value 1
- Wrong interval interpretation -
dp[i][j]is exclusive(i, j)
- LC 1039: Minimum Score Triangulation (Same pattern)
- LC 1547: Minimum Cost to Cut a Stick (Similar structure)
- LC 546: Remove Boxes (Harder variant)
https://leetcode.com/problems/minimum-score-triangulation-of-polygon/
Medium
- Array
- Dynamic Programming
Interval DP - Polygon Triangulation
IntervalDP
Given a convex polygon with n vertices labeled with values, triangulate it to minimize the sum of triangle scores, where each triangle's score is the product of its three vertex values.
For any edge (i, j) of the polygon, there's exactly one triangle that uses this edge. The third vertex k must be between i and j.
Choosing k as the third vertex:
- Forms triangle with vertices
i, k, jand scorevalues[i] * values[k] * values[j] - Leaves two smaller polygons:
[i, k]and[k, j]
def minScoreTriangulation(values: list) -> int:
n = len(values)
# dp[i][j] = min cost to triangulate polygon from i to j
dp = [[0] * n for _ in range(n)]
# Fill by increasing interval length (need at least 3 vertices)
for length in range(3, n + 1):
for i in range(n - length + 1):
j = i + length - 1
dp[i][j] = float('inf')
# Try all possible third vertices
for k in range(i + 1, j):
cost = values[i] * values[k] * values[j]
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cost)
return dp[0][n - 1]- Time: O(n³)
- Space: O(n²)
Polygon Triangulation shows the geometric interpretation:
- Natural visualization of interval DP
- Edge
(i, j)defines the subproblem - Split point
kis the third vertex
- Wrong loop bounds - Need
length >= 3for a triangle - Including endpoints -
kmust be strictly betweeniandj - Forgetting base case - Adjacent vertices have cost 0
- LC 312: Burst Balloons (Same structure)
- LC 1000: Minimum Cost to Merge Stones (Generalization)
https://leetcode.com/problems/minimum-cost-to-cut-a-stick/
Hard
- Array
- Dynamic Programming
- Sorting
Interval DP - Cutting Problems
IntervalDP
Given a stick of length n and positions where you must cut, each cut costs the length of the stick being cut. Find the minimum total cost to make all cuts.
The key is to think about which cut to make last in each segment:
- If we cut at position
klast in segment[i, j] - Cost = length of segment + cost of left + cost of right
- Cost =
(cuts[j] - cuts[i]) + dp[i][k] + dp[k][j]
Add boundary positions 0 and n to simplify.
def minCost(n: int, cuts: list) -> int:
# Add boundaries and sort
cuts = sorted([0] + cuts + [n])
m = len(cuts)
# dp[i][j] = min cost to cut segment between cuts[i] and cuts[j]
dp = [[0] * m for _ in range(m)]
# Fill by increasing gap between cut indices
for gap in range(2, m):
for i in range(m - gap):
j = i + gap
dp[i][j] = float('inf')
# Try all intermediate cuts
for k in range(i + 1, j):
cost = cuts[j] - cuts[i] # Length of current segment
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cost)
return dp[0][m - 1]- Time: O(m³) where m = len(cuts) + 2
- Space: O(m²)
This problem shows interval DP on transformed input:
- Need to add boundary positions
- Sort the cut positions
- Work with cut indices, not stick positions
- Forgetting boundaries - Must add 0 and n to cuts
- Not sorting - Cuts must be in order
- Wrong cost calculation - Cost is
cuts[j] - cuts[i], notj - i
- LC 312: Burst Balloons (Similar structure)
- LC 1000: Minimum Cost to Merge Stones
https://leetcode.com/problems/strange-printer/
Hard
- String
- Dynamic Programming
Interval DP - Character Printing
IntervalDP
A printer can print a sequence of the same character, and each print covers some substring. Given a string, find the minimum number of turns needed to print it.
For interval [i, j]:
- Base case: print
s[i]to cover entire interval, then recursively handle rest - Optimization: if
s[k] == s[i]for somek > i, we can "extend" the first print
When s[i] == s[j]:
dp[i][j] = dp[i][j-1](print s[i] to cover s[j] too)
When s[i] != s[j]:
- Try all split points:
dp[i][j] = min(dp[i][k] + dp[k+1][j])
def strangePrinter(s: str) -> int:
# Remove consecutive duplicates (they don't change answer)
s = ''.join(c for i, c in enumerate(s) if i == 0 or c != s[i-1])
n = len(s)
if n == 0:
return 0
# dp[i][j] = min turns to print s[i:j+1]
dp = [[0] * n for _ in range(n)]
# Base case: single character needs 1 turn
for i in range(n):
dp[i][i] = 1
# Fill by increasing length
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
# Worst case: print s[i] alone, then handle rest
dp[i][j] = dp[i + 1][j] + 1
# Optimization: extend s[i]'s print if s[k] == s[i]
for k in range(i + 1, j + 1):
if s[k] == s[i]:
left = dp[i + 1][k - 1] if k > i + 1 else 0
right = dp[k][j]
dp[i][j] = min(dp[i][j], left + right)
return dp[0][n - 1]- Time: O(n³)
- Space: O(n²)
Strange Printer shows non-standard interval DP:
- Optimization based on character matching
- Different recurrence structure
- Preprocessing to remove duplicates
- Not removing duplicates - Consecutive same chars waste computation
- Wrong base case - Single char needs 1 turn
- Missing the optimization - When
s[k] == s[i], we can combine prints
- LC 546: Remove Boxes (Similar character-based DP)
- LC 1000: Minimum Cost to Merge Stones
| Problem | Interval Meaning | Split Point | Merge Cost | Optimization |
|---|---|---|---|---|
| LC 312 Burst Balloons | Balloons in (i, j) | Last to burst | nums[i]*nums[k]*nums[j] |
Maximize |
| LC 1039 Polygon | Vertices i to j | Third vertex | v[i]*v[k]*v[j] |
Minimize |
| LC 1547 Cut Stick | Between cuts i, j | Cut position | cuts[j] - cuts[i] |
Minimize |
| LC 664 Strange Printer | Characters i to j | Matching char | N/A (special) | Minimize |
LC 312 Burst Balloons (Base)
│
│ Same structure, geometric interpretation
↓
LC 1039 Polygon Triangulation
│
│ Apply to cuts instead of items
│ Add boundary preprocessing
↓
LC 1547 Minimum Cost to Cut a Stick
│
│ Character-based optimization
│ Different recurrence
↓
LC 664 Strange Printer
| Problem | dp[i][j] Meaning |
|---|---|
| LC 312 | Max coins bursting balloons in (i, j) exclusive |
| LC 1039 | Min cost triangulating vertices [i, j] inclusive |
| LC 1547 | Min cost cutting between positions cuts[i] and cuts[j] |
| LC 664 | Min turns printing s[i:j+1] |
| Problem | Preprocessing |
|---|---|
| LC 312 | Add virtual balloons [1] at boundaries |
| LC 1039 | None |
| LC 1547 | Add 0 and n to cuts, sort |
| LC 664 | Remove consecutive duplicate characters |
| Problem | Outer Loop | Inner Split |
|---|---|---|
| LC 312 | length 2 to n | k from i+1 to j-1 |
| LC 1039 | length 3 to n | k from i+1 to j-1 |
| LC 1547 | gap 2 to m-1 | k from i+1 to j-1 |
| LC 664 | length 2 to n | k where s[k] == s[i] |
Start: Optimal way to process an interval?
│
▼
┌───────────────────┐
│ What operation? │
└───────────────────┘
│
┌───────┼───────┬───────────┐
▼ ▼ ▼ ▼
Remove Split Print Triangulate
items at point sequence polygon
│ │ │ │
▼ ▼ ▼ ▼
LC 312 LC 1547 LC 664 LC 1039
Burst Cut Strange Polygon
Balloons Stick Printer Score
- Removing items changes neighbors
- Need to consider "last operation"
- Boundaries provide context for removal
- Geometric interpretation exists
- Edge-based subproblem definition
- Third point splits into smaller polygons
- Cutting/splitting operations
- Cost depends on segment size
- Need to preprocess with boundaries
- Character/value matching matters
- Can "extend" operations for matching elements
- Non-standard split point selection
All Interval DP problems share:
- Time: O(n³) - three nested loops
- Space: O(n²) - 2D DP table
| Clue | Pattern |
|---|---|
| "burst balloons", "remove and merge" | LC 312 style |
| "triangulate polygon" | LC 1039 style |
| "minimum cost to cut/split" | LC 1547 style |
| "minimum operations to print/transform" | LC 664 style |
| "matrix chain multiplication" | Classic interval DP |
def burst_balloons_template(nums: list) -> int:
"""
Maximum coins from bursting all balloons.
Time: O(n³), Space: O(n²)
"""
nums = [1] + nums + [1]
n = len(nums)
dp = [[0] * n for _ in range(n)]
for length in range(2, n):
for i in range(n - length):
j = i + length
for k in range(i + 1, j):
coins = nums[i] * nums[k] * nums[j]
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + coins)
return dp[0][n - 1]Use for: LC 312, problems where removing item affects neighbors
def polygon_triangulation_template(values: list) -> int:
"""
Minimum score to triangulate polygon.
Time: O(n³), Space: O(n²)
"""
n = len(values)
dp = [[0] * n for _ in range(n)]
for length in range(3, n + 1):
for i in range(n - length + 1):
j = i + length - 1
dp[i][j] = float('inf')
for k in range(i + 1, j):
cost = values[i] * values[k] * values[j]
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cost)
return dp[0][n - 1]Use for: LC 1039, geometric interval DP
def cut_stick_template(n: int, cuts: list) -> int:
"""
Minimum cost to make all cuts.
Time: O(m³), Space: O(m²)
"""
cuts = sorted([0] + cuts + [n])
m = len(cuts)
dp = [[0] * m for _ in range(m)]
for gap in range(2, m):
for i in range(m - gap):
j = i + gap
dp[i][j] = float('inf')
for k in range(i + 1, j):
cost = cuts[j] - cuts[i]
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cost)
return dp[0][m - 1]Use for: LC 1547, cutting/splitting problems
def strange_printer_template(s: str) -> int:
"""
Minimum turns to print string.
Time: O(n³), Space: O(n²)
"""
# Remove consecutive duplicates
s = ''.join(c for i, c in enumerate(s) if i == 0 or c != s[i-1])
n = len(s)
if n == 0:
return 0
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
dp[i][j] = dp[i + 1][j] + 1
for k in range(i + 1, j + 1):
if s[k] == s[i]:
left = dp[i + 1][k - 1] if k > i + 1 else 0
right = dp[k][j]
dp[i][j] = min(dp[i][j], left + right)
return dp[0][n - 1]Use for: LC 664, character-matching optimization
| Problem Type | Template | Key Feature |
|---|---|---|
| Remove with neighbor effect | Template 1 | Add boundary elements |
| Polygon/geometric | Template 2 | Edge-based splitting |
| Cutting/splitting | Template 3 | Sort and add boundaries |
| Character matching | Template 4 | Special recurrence |
Document generated for NeetCode Practice Framework — API Kernel: interval_dp