Dynamic Programming
This note is part of my learning notes on Data Structures & Algorithms.
Dynamic Programming
A dynamic programming (DP) problem is a type of problem that can be solved by breaking it down into simpler subproblems, solving each subproblem just once, and storing their solutions - typically using a table to avoid redundant calculations. This approach is particularly useful for optimization problems, where the goal is to find the best solution among many possible ones.
Key Characteristics
- Optimal substructure: The problem can be broken down into smaller subproblems, and the optimal solution to the problem can be constructed from the optimal solutions of its subproblems.
- Overlapping subproblems: The problem can be divided into subproblems which are reused several times. Dynamic programming solves each subproblem once and stores the result to avoid redundant work.
SRTBOT Framework
The SRTBOT framework is a systematic approach to solve dynamic programming (DP) problems. It stands for:
- Subproblem
- Recurrence
- Topological order
- Base cases
- Original problem
- Time complexity
1. Subproblem
Define the subproblem of the DP. This usually involves defining what each entry in the DP table represents. Typically, the state will be an array or matrix where each element represents the solution to a subproblem.
2. Recurrence
Determine the recurrence relation that relates the current state to previous states. This step involves writing an equation or set of equations that describe how to compute the value of the current state based on previous states.
3. Topological order
Create a table (array or matrix) to store the computed values of subproblems and solve the problem in a bottom-up manner. Initialize this table based on the problem’s constraints.
4. Base cases
Identify and initialize the base cases of your DP table. Base cases are the simplest subproblems that can be solved directly without any further subproblem decomposition.
5. Original problem
Solve the original problem by solving all subproblems.
6. Time complexity
Analyze the time complexity of your solution by considering the size of the table and the number of operations required to fill each entry.
Example 1: Fibonacci Sequence
Compute the n-th Fibonacci number.
The Fibonacci sequence is defined as:
- for
- Subproblem will represent the i-th Fibonacci number
- Recurrence:
- Create a table (array) to store the computed Fibonacci numbers up to in topological order.
- Base cases
- Original problem:
- Time complexity:
- The table has entries
- Each entry computatin takes time
- Overall complexity is
Pseudocode:
def fibonacci(n): |
Example 2: Bowling Problem
In the Bowling problem, you are given a list of integers representing the number of pins knocked down in each throw. The goal is to compute the maximum score possible under the rules of bowling. The rules include the concepts of strikes, spares, and normal throws.
- A strike (all 10 pins knocked down in one throw) earns 10 points plus the sum of the next two throws.
- A spare (all 10 pins knocked down in two throws) earns 10 points plus the sum of the next throw.
= A normal throw earns the number of pins knocked down.
- Subproblem: will represent the maximum score achievable from the 0th throw to the i-th throw.
- Recurrence:
- If throw is a strike, then
- If throw and form a spare, then
- Normal throw, then
- Create a table (array) to store the computed scores up to each throw in topological order.
- Base case:
- Original problem:
- Time Complexity: Since we are filling each entry of the table in constant time, the overall time complexity is
Pseudocode
def bowling_score(throws): |