Skip to main content

Unit - 4

TITLE

Dynamic Programming​

1. Introduction​

1.1 Overview​

  • A dynamic-programming algorithm solves every sub-problem just once and then saves its answer in a table.
  • It avoids the work of re-computing the answer every time the sub-problem is encountered.
  • Example: If there are multiple items with weights and profits, and we need to fit them into a bag to maximize profit, DP helps to efficiently find the optimal answer.

2. Principle of Optimality​

2.1 Concept​

  • The dynamic programming algorithm obtains the solution using the principle of optimality.
  • It states that "in an optimal sequence of decisions or choices, each subsequence must also be optimal."
  • If it is not possible to apply the principle of optimality, then it is almost impossible to obtain the solution using the dynamic programming approach.

3. Generalized Solution using DP​

3.1 Four Steps​

  1. Characterize the structure of an optimal solution.
  2. Recursively define the value of an optimal solution.
  3. Compute the value of an optimal solution in a bottom-up fashion.
  4. Construct an optimal solution from the computed information.

4. 0/1 Knapsack Problem​

4.1 Concept​

  • We are given nn items, each with a weight wiw_i and a value viv_i, and a knapsack of capacity WW.
  • The goal is to maximize the value without exceeding the knapsack capacity.
  • Unlike Fractional Knapsack, we cannot break items (it's either 0 or 1).

4.2 Step-by-Step Explanation​

  1. Initialize a 2D array V[0…n][0…W]V[0 \dots n][0 \dots W].
  2. Set V[i][0]=0V[i][0] = 0 for 0≤i≤n0 \le i \le n.
  3. Iterate through items ii from 1 to nn and weights jj from 1 to WW:
    • If j<wij < w_i: The item can't be included. V[i][j]=V[i−1][j]V[i][j] = V[i - 1][j].
    • Else: Take the max of excluding the item or including the item. V[i][j]=max⁡(V[i−1][j],V[i−1][j−wi]+vi)V[i][j] = \max(V[i - 1][j], V[i - 1][j - w_i] + v_i).
  4. The answer is found at V[n][W]V[n][W].

4.3 Algorithm Complexity​

  • Time Complexity: O(n×W)O(n \times W), where nn is the number of items and WW is the knapsack capacity.
  • Space Complexity: O(n×W)O(n \times W) for the 2D DP table. Can be optimized to O(W)O(W) using a 1D array.

4.4 Algorithm​

💡 The Big Idea in Plain English​

Unlike Fractional Knapsack where you could cut items into pieces, here each item is a solid object (like a laptop or TV): you either take the whole thing (1) or leave it behind (0). For every item, you make a very simple comparison:

  1. Leave it: Keep the best profit you already made using previous items with current space.
  2. Take it: Add this item's value, plus the best profit you could make with whatever weight capacity remains in your bag. Compare both options, pick the maximum, and store it in a table so you never re-calculate!

👣 3 Easy Steps to Remember​

  1. Create a Grid: Rows = items (0…n0 \dots n), Columns = capacities (0…W0 \dots W).
    • With 0 items or 0 capacity, profit is always 00 (fill first row and column with 0).
  2. Decide for Each Item & Capacity:
    • For item ii and bag capacity ww:
    • Too heavy to fit? (wt[i]>wwt[i] > w): You have no choice →\to copy the profit directly from the row above (K[i−1,w]K[i-1, w]).
    • It fits!: Take the maximum of leaving it vs. taking it: K[i,w]=max⁡(val[i]+K[i−1,w−wt[i]],  K[i−1,w])K[i, w] = \max(val[i] + K[i-1, w - wt[i]], \; K[i-1, w])
  3. Read the Solution: The bottom-right corner K[n,W]K[n, W] is the absolute maximum profit!

🧠 Memory Trick: 0 or 1 choice →\to If too heavy: copy from above →\to If it fits: max⁡(Take it,Leave it)\max(\text{Take it}, \text{Leave it})! Time and space complexity: O(n×W)O(n \times W).

5. Binomial Coefficient​

5.1 Concept​

  • A binomial coefficient C(n,k)C(n, k) calculates the number of ways to choose kk items from nn items.
  • C(n,k)=C(n−1,k−1)+C(n−1,k)C(n, k) = C(n-1, k-1) + C(n-1, k).
  • DP avoids redundant calculations by storing intermediate values.

5.2 Step-by-Step Explanation​

  1. Initialize a 2D array CC of size (n+1)×(k+1)(n+1) \times (k+1).
  2. Loop ii from 0 to nn, and jj from 0 to min⁡(i,k)\min(i, k).
  3. Base cases: If j==0j == 0 or j==ij == i, then C[i][j]=1C[i][j] = 1.
  4. Otherwise: C[i][j]=C[i−1][j−1]+C[i−1][j]C[i][j] = C[i-1][j-1] + C[i-1][j].
  5. Return C[n][k]C[n][k].

5.3 Algorithm Complexity​

  • Time Complexity: O(n×k)O(n \times k)
  • Space Complexity: O(n×k)O(n \times k) or O(k)O(k) with a 1D array optimization.

5.4 Algorithm​

💡 The Big Idea in Plain English​

Think of Pascal's Triangle: Every number inside the triangle is simply the sum of the two numbers directly above it! To pick kk friends out of nn candidates (C(n,k)C(n, k)), imagine looking at one specific friend (say, Alex):

  • Alex is picked: You now need k−1k-1 more people from the remaining n−1n-1 friends (C(n−1,k−1)C(n-1, k-1)).
  • Alex is skipped: You still need kk people from the remaining n−1n-1 friends (C(n−1,k)C(n-1, k)). Adding both scenarios gives: C(n,k)=C(n−1,k−1)+C(n−1,k)C(n, k) = C(n-1, k-1) + C(n-1, k). Dynamic Programming builds this table row by row from top to bottom, avoiding slow factorials completely!

👣 3 Easy Steps to Remember​

  1. Set Edge Base Cases to 1:
    • Picking 0 items from ii is always 11 way (C[i,0]=1C[i, 0] = 1).
    • Picking all ii items from ii is always 11 way (C[i,i]=1C[i, i] = 1).
  2. Fill from Top to Bottom:
    • For every cell (i,j)(i, j): add the cell diagonally above-left and the cell directly above: C[i,j]=C[i−1,j−1]+C[i−1,j]C[i, j] = C[i-1, j-1] + C[i-1, j]
  3. Lookup the Value: C[n,k]C[n, k] contains the final binomial coefficient.

🧠 Memory Trick: Pascal's Triangle →\to Outer edges are 1 →\to Every inner number is the sum of the two above it! Time complexity: O(n×k)O(n \times k).

6. Floyd-Warshall Algorithm (All Pairs Shortest Path)​

6.1 Concept​

  • Finds the shortest paths between all pairs of vertices in a weighted graph.
  • Works for graphs with negative weight edges (but no negative weight cycles).

6.2 Step-by-Step Explanation​

  1. Initialize the solution matrix same as the input graph matrix as a first step.
  2. Update the solution matrix by considering all vertices as an intermediate vertex.
  3. The idea is to one by one pick all vertices and update all shortest paths which include the picked vertex as an intermediate vertex in the shortest path.
  4. If dist[i][j]>dist[i][k]+dist[k][j]dist[i][j] > dist[i][k] + dist[k][j], update dist[i][j]dist[i][j].

6.3 Algorithm Complexity​

  • Time Complexity: Θ(V3)\Theta(V^3)
  • Space Complexity: Θ(V2)\Theta(V^2)

6.4 Algorithm​

💡 The Big Idea in Plain English​

While Dijkstra finds paths from one source, Floyd-Warshall finds the shortest path between every pair of vertices (i,j)(i, j) at the same time! Think of booking flights between two cities ii and jj: You test whether taking a layover at city kk makes the trip faster: "Is direct flight i→ji \to j faster, or flight i→ki \to k plus flight k→jk \to j?" If the layover is cheaper, take the shortcut! By testing every possible layover city kk one by one, all shortest routes are solved.

👣 3 Easy Steps to Remember​

  1. Initialize the Distance Grid: Copy direct edge weights into table DD. If there is no road between ii and jj, set D[i,j]=∞D[i, j] = \infty. Distance from any city to itself is 00.
  2. Three Nested Loops (Layover kk MUST be the outer loop!):
    • Outer loop kk: Try vertex kk as the intermediate stop (1…V1 \dots V).
    • Middle loop ii: Try vertex ii as the starting point (1…V1 \dots V).
    • Inner loop jj: Try vertex jj as the ending destination (1…V1 \dots V).
  3. Shortcut Update Formula:
    • If going through kk is shorter: if (D[i,k]+D[k,j]<D[i,j])  ⟹  D[i,j]=D[i,k]+D[k,j]\text{if } (D[i, k] + D[k, j] < D[i, j]) \implies D[i, j] = D[i, k] + D[k, j]

🧠 Memory Trick: 3 nested loops with intermediate kk outside →D[i,j]=min⁡(direct,D[i,k]+D[k,j])\to D[i, j] = \min(\text{direct}, D[i, k] + D[k, j]). All-pairs shortest paths in Θ(V3)\Theta(V^3) time, handles negative edges (no negative cycles).

7. Matrix Chain Multiplication​

7.1 Concept​

  • Determines the most efficient way to multiply a given sequence of matrices.
  • The problem is not actually to perform the multiplications, but merely to decide the sequence of the matrix multiplications involved.
  • Many options exist because matrix multiplication is associative.

7.2 Step-by-Step Explanation​

  1. Let the input array p represent matrix dimensions such that matrix ii has dimension p[i−1]×p[i]p[i-1] \times p[i].
  2. Construct a 2D array m[n][n] to store the minimum scalar multiplications.
  3. For chain length L=2L = 2 to nn:
    • For i=1i = 1 to n−L+1n - L + 1, set j=i+L−1j = i + L - 1.
    • Compute m[i][j] by checking all possible partition points kk from ii to j−1j-1.
    • Cost is m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j].
    • Take the minimum of all possible kk's.
  4. m[1][n-1] holds the optimal cost.

7.3 Algorithm Complexity​

  • Time Complexity: O(n3)O(n^3)
  • Space Complexity: O(n2)O(n^2)

7.4 Algorithm​

💡 The Big Idea in Plain English​

Multiplying three matrices (A×B×C)(A \times B \times C) always produces the same numerical answer whether you do (AB)C(AB)C or A(BC)A(BC). BUT the number of scalar multiplications can vary wildly:

  • One way might take 7,500 operations.
  • The other way might take 75,000 operations (10 times slower!). We don't actually multiply the numbers here — we just figure out where to put brackets (parentheses) to do the least amount of work! We test every possible place to put a split/cut point kk and pick the cheapest cut.

👣 3 Easy Steps to Remember​

  1. Base Case (Single Matrix): Multiplying one matrix by itself costs 0 multiplications: m[i,i]=0m[i, i] = 0 (the main diagonal is all 0).
  2. Grow Chain Length LL from 2 to n−1n-1:
    • Solve all 2-matrix products first (AB,BC,CDAB, BC, CD).
    • Then solve all 3-matrix products (ABC,BCDABC, BCD).
    • Continue expanding the window until the entire chain is solved.
  3. Try Every Cut Point kk:
    • For any chain from matrix ii to jj, test splitting at every position kk from ii to j−1j-1: Cost=m[i,k]⏟cost of left+m[k+1,j]⏟cost of right+(pi−1×pk×pj)⏟cost to multiply the two resulting matrices\text{Cost} = \underbrace{m[i, k]}_{\text{cost of left}} + \underbrace{m[k+1, j]}_{\text{cost of right}} + \underbrace{(p_{i-1} \times p_k \times p_j)}_{\text{cost to multiply the two resulting matrices}}
    • Keep the minimum cost in m[i,j]m[i, j] and record the winning cut kk in s[i,j]s[i, j].
  4. Optimal Cost: m[1,n−1]m[1, n-1] gives the minimum total operations.

🧠 Memory Trick: Try every cut kk between ii and jj: Left cost + Right cost + (pi−1⋅pk⋅pj)(p_{i-1} \cdot p_k \cdot p_j). Solve by increasing chain length LL. Time: O(n3)O(n^3), Space: O(n2)O(n^2).

8. Longest Common Subsequence (LCS)​

8.1 Concept​

  • Given two sequences, find the length of the longest subsequence present in both of them.
  • A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.

8.2 Step-by-Step Explanation​

  1. Let the two strings be XX of length mm and YY of length nn.
  2. Create a 2D array L[m+1][n+1]L[m+1][n+1].
  3. Loop through the characters of XX and YY.
  4. If X[i−1]==Y[j−1]X[i-1] == Y[j-1], then L[i][j]=L[i−1][j−1]+1L[i][j] = L[i-1][j-1] + 1.
  5. If they are different, L[i][j]=max⁡(L[i−1][j],L[i][j−1])L[i][j] = \max(L[i-1][j], L[i][j-1]).
  6. L[m][n]L[m][n] contains the length of the LCS.

8.3 Algorithm Complexity​

  • Time Complexity: O(m×n)O(m \times n)
  • Space Complexity: O(m×n)O(m \times n)

8.4 Algorithm​

💡 The Big Idea in Plain English​

Think of how Git diff or DNA comparison works: You have two words (like X = "ABCBDAB" and Y = "BDCABA"). You want to find the longest sequence of letters that appear in both words in the same relative order, even if other letters are in between. As you compare letter X[i]X[i] with letter Y[j]Y[j]:

  • Letters Match? Awesome! That's +1+1 to your score: take the score from before these two letters existed (diagonally up-left) and add 11.
  • Letters Don't Match? You can't use both letters together. Pick the better choice between ignoring X[i]X[i] (cell above) or ignoring Y[j]Y[j] (cell to the left)!

👣 3 Easy Steps to Remember​

  1. Make a Grid: Rows for characters of XX, columns for characters of YY.
    • Row 0 and Column 0 are all 00 (comparing anything against an empty string gives 0).
  2. Fill Every Cell (i,j)(i, j):
    • Match (X[i]==Y[j]X[i] == Y[j]): Take the top-left diagonal value and add 1: c[i,j]=c[i−1,j−1]+1c[i, j] = c[i-1, j-1] + 1
    • Mismatch (X[i]≠Y[j]X[i] \ne Y[j]): Take the maximum of the cell above and the cell to the left: c[i,j]=max⁡(c[i−1,j],  c[i,j−1])c[i, j] = \max(c[i-1, j], \; c[i, j-1])
  3. Finish: Cell c[m,n]c[m, n] in the bottom-right corner gives the length of the LCS!

🧠 Memory Trick: Letters match? Go diagonal +1+ 1. Letters differ? Take max⁡(Up,Left)\max(\text{Up}, \text{Left})! Grid size: (m+1)×(n+1)(m+1) \times (n+1). Time & space: O(m×n)O(m \times n).