Spaces:
Sleeping
Sleeping
| DYNAMIC PROGRAMMING (DP) | |
| ======================== | |
| DP solves problems by breaking them into overlapping subproblems and storing results to avoid recomputation. | |
| Two approaches: | |
| - Top-Down (Memoization): recursion + cache | |
| - Bottom-Up (Tabulation): fill a table iteratively | |
| Key properties needed: | |
| 1. Optimal substructure: optimal solution built from optimal subproblems. | |
| 2. Overlapping subproblems: same subproblem solved multiple times. | |
| --- Fibonacci Number --- | |
| Classic DP example. fib(n) = fib(n-1) + fib(n-2) | |
| # Naive recursion - O(2^n) | |
| def fib_naive(n): | |
| if n <= 1: | |
| return n | |
| return fib_naive(n-1) + fib_naive(n-2) | |
| # Memoization - O(n) | |
| def fib_memo(n, memo={}): | |
| if n in memo: | |
| return memo[n] | |
| if n <= 1: | |
| return n | |
| memo[n] = fib_memo(n-1, memo) + fib_memo(n-2, memo) | |
| return memo[n] | |
| # Tabulation - O(n) | |
| def fib_dp(n): | |
| if n <= 1: | |
| return n | |
| dp = [0] * (n + 1) | |
| dp[1] = 1 | |
| for i in range(2, n + 1): | |
| dp[i] = dp[i-1] + dp[i-2] | |
| return dp[n] | |
| print(fib_dp(10)) # Output: 55 | |
| print(fib_dp(20)) # Output: 6765 | |
| --- 0/1 Knapsack Problem --- | |
| Given items with weights and values, maximize value in a bag of capacity W. | |
| Each item can be taken once (0/1). Time: O(n*W). Space: O(n*W). | |
| def knapsack(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): | |
| # Don't take item i | |
| dp[i][w] = dp[i-1][w] | |
| # Take item i if it fits | |
| 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] | |
| weights = [1, 2, 3, 5] | |
| values = [1, 6, 10, 16] | |
| capacity = 7 | |
| print(knapsack(weights, values, capacity)) # Output: 22 | |
| --- Longest Common Subsequence (LCS) --- | |
| Find length of longest subsequence present in both strings. | |
| Time: O(m*n). Space: O(m*n). | |
| def lcs(s1, s2): | |
| m, n = len(s1), len(s2) | |
| dp = [[0] * (n + 1) for _ in range(m + 1)] | |
| for i in range(1, m + 1): | |
| for j in range(1, n + 1): | |
| if s1[i-1] == s2[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] | |
| print(lcs("ABCBDAB", "BDCABA")) # Output: 4 (BCBA) | |
| --- Longest Increasing Subsequence (LIS) --- | |
| Find length of longest strictly increasing subsequence. | |
| Time: O(n^2). Space: O(n). | |
| def lis(arr): | |
| n = len(arr) | |
| dp = [1] * n | |
| for i in range(1, n): | |
| for j in range(i): | |
| if arr[j] < arr[i]: | |
| dp[i] = max(dp[i], dp[j] + 1) | |
| return max(dp) | |
| print(lis([10, 9, 2, 5, 3, 7, 101, 18])) # Output: 4 (2,3,7,101) | |
| --- Coin Change Problem --- | |
| Find minimum number of coins to make amount. Coins can be reused. | |
| Time: O(amount * len(coins)). Space: O(amount). | |
| def coin_change(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 | |
| print(coin_change([1, 5, 6, 9], 11)) # Output: 2 (coins 5+6) | |
| print(coin_change([2], 3)) # Output: -1 (impossible) | |
| --- Subset Sum Problem --- | |
| Check if any subset of array sums to target. | |
| Time: O(n * target). Space: O(n * target). | |
| def subset_sum(arr, target): | |
| n = len(arr) | |
| dp = [[False] * (target + 1) for _ in range(n + 1)] | |
| for i in range(n + 1): | |
| dp[i][0] = True # sum 0 always possible | |
| for i in range(1, n + 1): | |
| for j in range(1, target + 1): | |
| dp[i][j] = dp[i-1][j] | |
| if arr[i-1] <= j: | |
| dp[i][j] = dp[i][j] or dp[i-1][j - arr[i-1]] | |
| return dp[n][target] | |
| print(subset_sum([3, 34, 4, 12, 5, 2], 9)) # Output: True (4+5) | |
| print(subset_sum([3, 34, 4, 12, 5, 2], 30)) # Output: False | |
| --- Matrix Chain Multiplication --- | |
| Find minimum operations to multiply a chain of matrices. | |
| Time: O(n^3). Space: O(n^2). | |
| def matrix_chain(dims): | |
| n = len(dims) - 1 | |
| 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 = dp[i][k] + dp[k+1][j] + dims[i]*dims[k+1]*dims[j+1] | |
| dp[i][j] = min(dp[i][j], cost) | |
| return dp[0][n-1] | |
| dims = [40, 20, 30, 10, 30] | |
| print(matrix_chain(dims)) # Output: 26000 | |
| --- Edit Distance (Levenshtein) --- | |
| Minimum operations (insert, delete, replace) to convert one string to another. | |
| Time: O(m*n). Space: O(m*n). | |
| def edit_distance(s1, s2): | |
| m, n = len(s1), len(s2) | |
| 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 s1[i-1] == s2[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] | |
| print(edit_distance("horse", "ros")) # Output: 3 | |
| print(edit_distance("intention", "execution")) # Output: 5 | |