2D Dynamic Programming Interview Patterns (2025)

2D Dynamic Programming Interview Patterns (2025)

Two-dimensional DP problems are common at Google, Meta, Amazon, and Microsoft. They involve grids, two-sequence comparisons, or interval-based problems. Mastering 2D DP unlocks a large category of medium and hard LeetCode problems.

Pattern 1: Grid Traversal DP

# Unique Paths (LeetCode 62) - count paths from top-left to bottom-right
def uniquePaths(m, n):
    dp = [[1] * n for _ in range(m)]
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = dp[i-1][j] + dp[i][j-1]
    return dp[m-1][n-1]
# Time O(mn), Space O(mn) -> optimize to O(n) with 1D array

# Minimum Path Sum (LeetCode 64)
def minPathSum(grid):
    m, n = len(grid), len(grid[0])
    dp = [[0]*n for _ in range(m)]
    dp[0][0] = grid[0][0]
    for j in range(1, n): dp[0][j] = dp[0][j-1] + grid[0][j]
    for i in range(1, m): dp[i][0] = dp[i-1][0] + grid[i][0]
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
    return dp[m-1][n-1]

Pattern 2: Two-Sequence DP

# Longest Common Subsequence (LeetCode 1143)
def longestCommonSubsequence(text1, text2):
    m, n = len(text1), len(text2)
    dp = [[0]*(n+1) for _ in range(m+1)]
    for i in range(1, m+1):
        for j in range(1, n+1):
            if text1[i-1] == text2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp[m][n]

# Edit Distance (LeetCode 72)
def minDistance(word1, word2):
    m, n = len(word1), len(word2)
    dp = [[0]*(n+1) for _ in range(m+1)]
    for i in range(m+1): dp[i][0] = i
    for j in range(n+1): dp[0][j] = j
    for i in range(1, m+1):
        for j in range(1, n+1):
            if word1[i-1] == word2[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = 1 + min(dp[i-1][j],   # delete
                                    dp[i][j-1],   # insert
                                    dp[i-1][j-1]) # replace
    return dp[m][n]

Pattern 3: Interval DP

Interval DP fills a table where dp[i][j] represents the answer for a subproblem on range [i, j]. Process by increasing interval length.

# Minimum Cost to Merge Stones / Burst Balloons pattern
# Burst Balloons (LeetCode 312)
def maxCoins(nums):
    nums = [1] + nums + [1]
    n = len(nums)
    dp = [[0]*n for _ in range(n)]
    # dp[i][j] = max coins from bursting all balloons between i and j (exclusive)
    for length in range(2, n):
        for i in range(n - length):
            j = i + length
            for k in range(i+1, j):
                dp[i][j] = max(dp[i][j],
                               dp[i][k] + nums[i]*nums[k]*nums[j] + dp[k][j])
    return dp[0][n-1]

# Palindrome Partitioning II (LeetCode 132) - min cuts
def minCut(s):
    n = len(s)
    # Precompute palindrome table
    is_pal = [[False]*n for _ in range(n)]
    for i in range(n-1, -1, -1):
        for j in range(i, n):
            if s[i] == s[j] and (j - i <= 2 or is_pal[i+1][j-1]):
                is_pal[i][j] = True
    # dp[i] = min cuts for s[:i+1]
    dp = list(range(-1, n))
    for i in range(n):
        for j in range(i+1):
            if is_pal[j][i]:
                dp[i+1] = min(dp[i+1], dp[j] + 1)
    return dp[n]

Pattern 4: Knapsack Variants

# 0/1 Knapsack: each item used at most once
def knapsack_01(weights, values, capacity):
    n = len(weights)
    dp = [[0]*(capacity+1) for _ in range(n+1)]
    for i in range(1, n+1):
        for w in range(capacity+1):
            dp[i][w] = dp[i-1][w]  # don't take item i
            if weights[i-1] <= w:
                dp[i][w] = max(dp[i][w], dp[i-1][w-weights[i-1]] + values[i-1])
    return dp[n][capacity]

# Unbounded Knapsack: each item used unlimited times
def knapsack_unbounded(weights, values, capacity):
    dp = [0] * (capacity + 1)
    for w in range(1, capacity + 1):
        for i in range(len(weights)):
            if weights[i] <= w:
                dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
    return dp[capacity]

# Coin Change (LeetCode 322) - unbounded knapsack variant
def coinChange(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)
    return dp[amount] if dp[amount] != float('inf') else -1

Pattern 5: Stock Problems (State Machine DP)

# Best Time to Buy and Sell Stock with Cooldown (LeetCode 309)
def maxProfit_cooldown(prices):
    hold = -float('inf')  # max profit while holding a stock
    sold = 0              # max profit day after selling (cooldown)
    rest = 0              # max profit while resting (not holding, not cooldown)
    for price in prices:
        prev_hold, prev_sold, prev_rest = hold, sold, rest
        hold = max(prev_hold, prev_rest - price)  # buy from rest state
        sold = prev_hold + price                   # sell today
        rest = max(prev_rest, prev_sold)           # rest or come from sold
    return max(sold, rest)

# Stock with at most K transactions (LeetCode 188)
def maxProfit_k(k, prices):
    n = len(prices)
    if not prices or k == 0: return 0
    if k >= n // 2:  # can make unlimited transactions
        return sum(max(prices[i+1]-prices[i], 0) for i in range(n-1))
    dp = [[0]*n for _ in range(k+1)]
    for t in range(1, k+1):
        max_sofar = -prices[0]
        for i in range(1, n):
            dp[t][i] = max(dp[t][i-1], prices[i] + max_sofar)
            max_sofar = max(max_sofar, dp[t-1][i] - prices[i])
    return dp[k][n-1]

Pattern 6: Matrix Chain / Tree DP in 2D

# Maximal Square (LeetCode 221)
def maximalSquare(matrix):
    m, n = len(matrix), len(matrix[0])
    dp = [[0]*n for _ in range(m)]
    max_side = 0
    for i in range(m):
        for j in range(n):
            if matrix[i][j] == '1':
                if i == 0 or j == 0:
                    dp[i][j] = 1
                else:
                    dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
                max_side = max(max_side, dp[i][j])
    return max_side * max_side

# Largest Rectangle in Histogram (LeetCode 84) - used in maximalRectangle
def largestRectangleArea(heights):
    stack = []
    max_area = 0
    for i, h in enumerate(heights + [0]):
        while stack and heights[stack[-1]] >= h:
            height = heights[stack.pop()]
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, height * width)
        stack.append(i)
    return max_area

Key Problems to Practice

  • LeetCode 62, 63, 64 – Unique paths and minimum path sum (grid DP)
  • LeetCode 1143 – Longest common subsequence
  • LeetCode 72 – Edit distance
  • LeetCode 312 – Burst balloons (interval DP)
  • LeetCode 322, 518 – Coin change (knapsack)
  • LeetCode 309, 188 – Stock problems (state machine)
  • LeetCode 221 – Maximal square
  • LeetCode 85 – Maximal rectangle (histogram approach)

Scroll to Top