Lesson 39 / 42

Advanced Dynamic Programming

Three interview staples — 0/1 Knapsack, Longest Common Subsequence, and Longest Increasing Subsequence.

0/1 Knapsack

For each item, decide to take or skip it. dp[w] tracks the best value achievable with capacity w; iterate weight backward so each item is used at most once.

def knapsack(weights, values, capacity):
    dp = [0] * (capacity + 1)
    for wt, val in zip(weights, values):
        for w in range(capacity, wt - 1, -1):
            dp[w] = max(dp[w], dp[w - wt] + val)
    return dp[capacity]

Output:

knapsack([1, 3, 4, 5], [1, 4, 5, 7], 7)  # 9

Longest Common Subsequence

dp[i][j] is the LCS length of text1[:i] and text2[:j]. Matching characters extend the diagonal; otherwise take the best of skipping one character from either string.

def lcs(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]

Output:

lcs('abcde', 'ace')  # 3 ('ace')

Longest Increasing Subsequence

The O(n^2) version tries every previous index. A faster O(n log n) version keeps a tails array of the smallest tail for each subsequence length, using binary search to place each new number.

import bisect

def length_of_lis(nums):
    tails = []
    for x in nums:
        i = bisect.bisect_left(tails, x)
        if i == len(tails):
            tails.append(x)
        else:
            tails[i] = x
    return len(tails)

Output:

length_of_lis([10, 9, 2, 5, 3, 7, 101, 18])  # 4  ([2,3,7,101] etc.)

Recognize the shape

Two strings/sequences and a comparison → LCS-style 2D DP. A single sequence with 'increasing/decreasing' → LIS-style. A set of items with a capacity limit → knapsack-style.