Dynamic Programming on Intervals Interview Patterns

What is Interval DP?

Interval DP is a dynamic programming pattern where you optimize over all sub-intervals of a range. The key idea: solve the smallest intervals first, then build up to the full range. This naturally leads to an O(n^3) template – O(n^2) sub-intervals, each requiring an O(n) scan over split points.

The core template:

for length in range(2, n+1):
    for l in range(n - length + 1):
        r = l + length - 1
        for k in range(l, r):
            dp[l][r] = min/max(dp[l][r], dp[l][k] + dp[k+1][r] + cost(l, k, r))

Key insight for all interval DP: Define what the last operation is – the last balloon burst, the last cut made, the last matrix multiplication. This transforms an exponential search into a polynomial recurrence.

Burst Balloons (LC 312)

Given an array of balloons with values, bursting balloon i gives coins nums[i-1] * nums[i] * nums[i+1]. Maximize total coins.

The trick: Think “last balloon to burst” instead of “first balloon to burst”. If balloon k is the last burst in interval [l, r], then when it bursts, l-1 and r+1 are still present (they are the boundaries). This gives a clean recurrence without cascading dependencies.

dp[l][r] = max coins from bursting all balloons strictly inside the padded interval [l, r].

def maxCoins(nums):
    nums = [1] + nums + [1]
    n = len(nums)
    dp = [[0] * n for _ in range(n)]

    for length in range(2, n):
        for l in range(0, n - length):
            r = l + length
            for k in range(l + 1, r):
                coins = nums[l] * nums[k] * nums[r]
                dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + coins)

    return dp[0][n - 1]

Time: O(n^3), Space: O(n^2).

Strange Printer (LC 664)

A printer can print a sequence of the same character in one turn, overwriting what was there. Find the minimum number of turns to print a given string.

Key insight: dp[i][j] = minimum turns to print s[i..j]. Base: dp[i][i] = 1. If s[i] == s[k] for some k in (i, j], we can extend the print of s[i] to also cover position k without an extra turn.

def strangePrinter(s):
    n = len(s)
    dp = [[0] * n for _ in range(n)]

    for i in range(n - 1, -1, -1):
        dp[i][i] = 1
        for j in range(i + 1, n):
            # Start with: print s[i] separately, then handle s[i+1..j]
            dp[i][j] = 1 + dp[i + 1][j] if i + 1 <= j else 1
            for k in range(i + 1, j + 1):
                if s[k] == s[i]:
                    # Merge the turn for s[i] with the turn that prints s[k]
                    val = dp[i + 1][k] + dp[k][j] if i + 1 <= k else dp[k][j]
                    dp[i][j] = min(dp[i][j], val)

    return dp[0][n - 1]

Time: O(n^3), Space: O(n^2).

Minimum Cost to Cut a Stick (LC 1547)

A stick of length n with a set of required cut positions. The cost of a cut equals the current length of the stick being cut. Minimize total cost.

Setup: Add 0 and n to the cuts array and sort. dp[i][j] = min cost to make all cuts between cuts[i] and cuts[j]. The cost of making a cut at position cuts[k] within this segment is cuts[j] - cuts[i] (the current segment length).

def minCost(n, cuts):
    cuts = sorted([0] + cuts + [n])
    m = len(cuts)
    dp = [[0] * m for _ in range(m)]

    for length in range(2, m):
        for i in range(m - length):
            j = i + length
            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]

Time: O(m^3) where m = len(cuts) + 2, Space: O(m^2).

Matrix Chain Multiplication (Classic)

Given matrices A1, A2, …, An where Ai has dimensions dims[i-1] x dims[i], find the parenthesization that minimizes total scalar multiplications.

dp[i][j] = min multiplications to compute the product of matrices i through j. Base: dp[i][i] = 0. Try all split points k in [i, j-1]: multiply (i..k) * (k+1..j), costing dims[i-1] * dims[k] * dims[j].

def matrixChain(dims):
    n = len(dims) - 1  # number of matrices
    dp = [[0] * n for _ in range(n)]

    for length in range(2, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            dp[i][j] = float('inf')
            for k in range(i, j):
                cost = dims[i] * dims[k + 1] * dims[j + 1]
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + cost)

    return dp[0][n - 1]

Time: O(n^3), Space: O(n^2). This is the canonical interval DP problem – all others are variations on this structure.

Palindrome Partitioning III (LC 1278)

Split string s into exactly k non-overlapping palindromic substrings with minimum total character changes.

Precompute cost[i][j] = min changes to make s[i..j] a palindrome (use two-pointer: add 1 when s[l] != s[r]). Then dp[part][i] = min cost to split s[0..i] into part palindromes.

def palindromePartition(s, k):
    n = len(s)
    # cost[i][j] = min changes to make s[i..j] a palindrome
    cost = [[0] * n for _ in range(n)]
    for length in range(2, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            cost[i][j] = cost[i+1][j-1] + (0 if s[i] == s[j] else 1)

    # dp[part][i] = min cost to split s[0..i] into part palindromes
    INF = float('inf')
    dp = [[INF] * n for _ in range(k + 1)]
    for i in range(n):
        dp[1][i] = cost[0][i]

    for part in range(2, k + 1):
        for i in range(part - 1, n):
            for j in range(part - 2, i):
                if dp[part-1][j] < INF:
                    dp[part][i] = min(dp[part][i], dp[part-1][j] + cost[j+1][i])

    return dp[k][n - 1]

Time: O(k * n^2), Space: O(k * n).

The Universal Interval DP Template

Every interval DP problem maps onto this template:

# Step 1: Initialize dp table (usually 0 or INF depending on min/max)
dp = [[0] * n for _ in range(n)]

# Step 2: Fill by increasing interval length
for length in range(2, n + 1):
    for l in range(n - length + 1):
        r = l + length - 1
        dp[l][r] = float('inf')  # or -inf for max problems
        # Step 3: Try all split points
        for k in range(l, r):
            dp[l][r] = min(dp[l][r], dp[l][k] + dp[k+1][r] + cost(l, k, r))

Variations to watch for in interviews:

  • Asymmetric split: Burst balloons uses dp[l][k] + dp[k][r] (k is not a split point but the last element) – adjust the loop bounds accordingly.
  • Precomputed cost: Matrix chain and stick cutting have O(1) cost functions. Palindrome partitioning precomputes a cost table first.
  • Multiple dimensions: Palindrome Partitioning III adds a partition count dimension – dp[parts][r] rather than dp[l][r].
  • Bottom-up vs top-down: Both work. Top-down with memoization is often easier to implement correctly; bottom-up by length is more cache-friendly.
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”What is interval DP and when do you use it?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Interval DP solves optimization problems over contiguous sub-sequences of a sequence. The key insight: to optimize over the full range [0, n-1], first solve all sub-problems over shorter ranges, then combine. The "last operation" insight: choose what happens last in the range (last balloon burst, last cut, last matrix multiplication) and express the cost in terms of already-solved subproblems. Template: for length from 2 to n: for l from 0 to n-length: r = l+length-1: for k from l to r-1: dp[l][r] = optimize(dp[l][k] + dp[k+1][r] + cost(l,k,r)). Time O(n^3), space O(n^2). Use when: the problem asks for the optimal way to split or combine a sequence, and the cost of the split depends on the boundary elements.”}},{“@type”:”Question”,”name”:”How do you solve Burst Balloons (LC 312)?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Choosing which balloon to burst first is hard because it changes neighbors. Instead, think about which balloon is burst last in the range [l, r]. If balloon k is burst last: when k is burst, its left neighbor is balloons[l-1] and right neighbor is balloons[r+1] (all others in [l,r] have been burst). Coins = balloons[l-1] * balloons[k] * balloons[r+1]. Sub-problems: dp[l][k-1] (burst all balloons to the left of k in [l,k-1]) and dp[k+1][r] (burst all to the right). Recurrence: dp[l][r] = max over k in [l,r] of (dp[l][k-1] + balloons[l-1]*balloons[k]*balloons[r+1] + dp[k+1][r]). Add boundary balloons[0]=1 and balloons[n+1]=1. Fill by increasing interval length. O(n^3).”}},{“@type”:”Question”,”name”:”How do you solve Minimum Cost to Cut a Stick (LC 1547)?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Add 0 and stick length to the cuts array and sort. Now the problem becomes: given a set of cut points, make all cuts to minimize total cost (cost of each cut = length of the stick segment being cut). dp[i][j] = minimum cost to make all cuts between cuts[i] and cuts[j]. Base case: dp[i][i+1] = 0 (no cuts between adjacent positions). Transition: try each cut position k between i and j. Cost of this cut = cuts[j] – cuts[i] (the length of the current segment). dp[i][j] = min over k in (i+1..j-1) of (cuts[j] – cuts[i] + dp[i][k] + dp[k][j]). Fill by increasing gap size (j-i). Answer: dp[0][n-1] where n-1 is the last index in the augmented cuts array.”}},{“@type”:”Question”,”name”:”What is the key insight for all interval DP problems?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”The universal insight: choose what operation happens last within the interval, and express the problem as: cost = (left sub-problem) + (right sub-problem) + (cost of the last operation). The "last operation" framing inverts the natural forward thinking and enables clean recursion. In Burst Balloons: last balloon burst. In Matrix Chain Multiplication: last matrix multiply. In Minimum Cost to Cut: last cut (equivalently, first cut – since every cut eventually gets made). In Optimal BST: last root chosen. The boundary elements at the time of the last operation are always the endpoints of the current interval (since everything inside has been handled by sub-problems). Once you identify the "last operation" and how it interacts with the interval boundaries, writing the recurrence is straightforward.”}},{“@type”:”Question”,”name”:”How does Matrix Chain Multiplication work as interval DP?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Given n matrices with dimensions p[0] x p[1], p[1] x p[2], …, p[n-1] x p[n], find the minimum number of scalar multiplications to compute the product. dp[i][j] = minimum multiplications to compute matrix i through j. Base: dp[i][i] = 0 (single matrix, no multiplication). Transition: try each split point k in [i, j-1]. Multiplying (i..k) * (k+1..j) costs p[i] * p[k+1] * p[j+1] (dimensions of the two resulting matrices). dp[i][j] = min over k of (dp[i][k] + dp[k+1][j] + p[i] * p[k+1] * p[j+1]). Fill by increasing chain length from 2 to n. The optimal parenthesization can reduce multiplications by orders of magnitude: for 3 matrices 10×100, 100×5, 5×50, the difference between the two parenthesizations is 7500 vs 75000 multiplications.”}}]}

Meta coding interviews include interval DP patterns. See common questions for Meta interview: interval DP and optimization problems.

Databricks interviews include advanced DP patterns. See patterns for Databricks interview: dynamic programming and optimization.

Apple coding rounds include interval DP problems. See patterns for Apple interview: interval DP and sequence optimization.

Scroll to Top