ScholarQuill logoScholarQuillUniversity Notes
  • Notes
  • Past Papers
  • Blogs
  • Todo
Login
ScholarQuill logoScholarQuillUniversity Notes
Login
NotesPast PapersBlogsTodo
More
SubjectsDiscussionCGPA CalculatorGPA CalculatorStudent PortalCourse Outline
About
About usPrivacy PolicyReportContact
Notes
Past Papers
Blogs
Todo
Analytics
    Current Subject
    🧩
    Data Structures
    CSI-413
    Progress0 / 19 topics
    Topics
    1. Introduction to Data Structures2. Arrays3. Stacks4. Queues5. Priority Queues6. Linked Lists7. Trees8. Graphs9. Recursion10. Sorting Algorithms11. Searching Algorithms12. Hashing13. Storage and Retrieval Properties and Techniques for Data Structures14. Algorithm Complexity15. Polynomial and Intractable Algorithms16. Classes of Efficient Algorithms17. Divide and Conquer18. Dynamic Programming19. Greedy Algorithms
    CSI-413›Dynamic Programming
    Data StructuresTopic 18 of 19

    Dynamic Programming

    10 minread
    1,686words
    Intermediatelevel

    Dynamic Programming (DP)

    Dynamic Programming (DP) is a powerful algorithmic technique used to solve optimization problems by breaking them down into simpler subproblems. It is particularly useful when a problem has overlapping subproblems and optimal substructure, which means the solution to the problem can be constructed from the solutions to smaller subproblems. DP avoids redundant computations by storing the results of subproblems, which can be reused in solving larger problems. This makes DP more efficient than naive recursive approaches.

    Key Concepts of Dynamic Programming

    1. Optimal Substructure: The problem can be solved by combining solutions to its subproblems. If the solution to a problem involves solving smaller instances of the same problem, it has optimal substructure.

    2. Overlapping Subproblems: The problem can be broken down into smaller subproblems that are solved multiple times. Dynamic programming exploits this property by storing the results of subproblems in a table (memoization or tabulation), thus eliminating the need to recompute them.

    3. Memoization vs Tabulation:

      • Memoization (Top-Down Approach): This is the process of storing the results of expensive function calls and returning the cached result when the same inputs occur again. It involves recursion, but avoids recalculating results by storing the intermediate solutions in a table (cache).
      • Tabulation (Bottom-Up Approach): This approach involves solving the problem iteratively and storing the results in a table. It starts with the smallest subproblems and builds up to the solution of the original problem.

    Steps in Solving a Problem Using Dynamic Programming

    1. Define the subproblem: Identify the smaller subproblems that can be solved independently and combined to solve the larger problem.

    2. Recurrence relation: Develop a recurrence relation that expresses the solution to a problem in terms of solutions to smaller subproblems.

    3. Base cases: Identify the simplest subproblems (usually with known solutions) that will serve as the foundation for solving larger subproblems.

    4. Compute solutions: Using memoization or tabulation, compute the solutions to the subproblems, building up to the solution of the original problem.

    5. Solution extraction: Once the subproblems are solved, the result for the original problem is typically stored in the last cell of the DP table or can be directly extracted from the cache.


    Examples of Dynamic Programming Problems

    1. Fibonacci Sequence

    • Problem: The nth Fibonacci number is the sum of the two preceding numbers, with the first two numbers being 0 and 1. Find the nth Fibonacci number.
    • Recurrence Relation: F(n)=F(n−1)+F(n−2),with F(0)=0 and F(1)=1F(n) = F(n-1) + F(n-2), \quad \text{with } F(0) = 0 \text{ and } F(1) = 1F(n)=F(n−1)+F(n−2),with F(0)=0 and F(1)=1
    • Time Complexity:
      • Memoization: O(n)O(n)O(n)
      • Tabulation: O(n)O(n)O(n)
    • Example:
      Input: n = 6
      Output: 8 (Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8)
      
    Memoization (Top-Down):
    int fib(int n, vector<int>& dp) {
        if (n <= 1) return n;
        if (dp[n] != -1) return dp[n];  // Return cached result
        return dp[n] = fib(n - 1, dp) + fib(n - 2, dp);
    }
    
    Tabulation (Bottom-Up):
    int fib(int n) {
        vector<int> dp(n + 1, 0);
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
    

    2. 0/1 Knapsack Problem

    • Problem: Given a set of items, each with a weight and value, determine the maximum value you can carry in a knapsack with a given weight capacity.
    • Recurrence Relation: K(i,W)=max⁡(K(i−1,W),value[i]+K(i−1,W−weight[i]))K(i, W) = \max(K(i-1, W), \text{value}[i] + K(i-1, W - \text{weight}[i]))K(i,W)=max(K(i−1,W),value[i]+K(i−1,W−weight[i])) where K(i,W)K(i, W)K(i,W) is the maximum value that can be obtained with the first iii items and a knapsack of weight WWW.
    • Time Complexity: O(nW)O(nW)O(nW) where nnn is the number of items and WWW is the capacity of the knapsack.
    Tabulation (Bottom-Up):
    int knapsack(int W, vector<int>& weights, vector<int>& values, int n) {
        vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));
        
        for (int i = 1; i <= n; i++) {
            for (int w = 1; w <= W; w++) {
                if (weights[i - 1] <= w) {
                    dp[i][w] = max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]]);
                } else {
                    dp[i][w] = dp[i - 1][w];
                }
            }
        }
        return dp[n][W];
    }
    

    3. Longest Common Subsequence (LCS)

    • Problem: Given two strings, find the length of the longest subsequence that is common to both strings.
    • Recurrence Relation: LCS(i,j)={0if i=0 or j=01+LCS(i−1,j−1)if s1[i]=s2[j]max⁡(LCS(i−1,j),LCS(i,j−1))otherwiseLCS(i, j) = \begin{cases} 0 & \text{if } i = 0 \text{ or } j = 0 \\ 1 + LCS(i-1, j-1) & \text{if } s1[i] = s2[j] \\ \max(LCS(i-1, j), LCS(i, j-1)) & \text{otherwise} \end{cases}LCS(i,j)=⎩⎨⎧​01+LCS(i−1,j−1)max(LCS(i−1,j),LCS(i,j−1))​if i=0 or j=0if s1[i]=s2[j]otherwise​
    • Time Complexity: O(m×n)O(m \times n)O(m×n), where mmm and nnn are the lengths of the two strings.
    Tabulation (Bottom-Up):
    int lcs(string s1, string s2) {
        int m = s1.length(), n = s2.length();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                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];
    }
    

    4. Matrix Chain Multiplication

    • Problem: Given a sequence of matrices, find the most efficient way to multiply them. The problem is to determine the order of matrix multiplications that minimizes the total number of scalar multiplications.
    • Recurrence Relation: m[i,j]=min⁡k{m[i,k]+m[k+1,j]+p[i−1]×p[k]×p[j]}m[i, j] = \min_{k} \{ m[i, k] + m[k+1, j] + p[i-1] \times p[k] \times p[j] \}m[i,j]=kmin​{m[i,k]+m[k+1,j]+p[i−1]×p[k]×p[j]} where p[i−1]×p[i]×p[j]p[i-1] \times p[i] \times p[j]p[i−1]×p[i]×p[j] is the cost of multiplying the matrices iii to jjj.
    • Time Complexity: O(n3)O(n^3)O(n3), where nnn is the number of matrices.
    Tabulation (Bottom-Up):
    int matrixChainOrder(vector<int>& p, int n) {
        vector<vector<int>> dp(n, vector<int>(n, 0));
        
        for (int len = 2; len < n; len++) {
            for (int i = 1; i < n - len + 1; i++) {
                int j = i + len - 1;
                dp[i][j] = INT_MAX;
                for (int k = i; k < j; k++) {
                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + p[i - 1] * p[k] * p[j]);
                }
            }
        }
        return dp[1][n - 1];
    }
    

    Advantages of Dynamic Programming

    • Efficiency: It avoids redundant calculations by storing results of subproblems, significantly reducing the time complexity compared to naive recursive methods.
    • Optimal Solution: DP guarantees finding the optimal solution to problems that have optimal substructure and overlapping subproblems.
    • Scalability: For many complex problems, DP provides a feasible solution where brute force approaches would be computationally infeasible.

    Disadvantages of Dynamic Programming

    • Space Complexity: DP often requires additional space to store intermediate results (e.g., tables or arrays), which may lead to high space complexity.
    • Complexity: Understanding and form
    Previous topic 17
    Divide and Conquer
    Next topic 19
    Greedy Algorithms

    Past Papers

    Open this section to load past papers

    Click on Show Past Papers to see past papers.
    On This Page
      Reading Stats
      Est. reading time10 min
      Word count1,686
      Code examples0
      DifficultyIntermediate