CSE 373 WI25 Section 9
Dynamic Programming
Created by Sravani, best 373 TA ever
Agenda
Announcements
Mon (3/03) | Tues (3/04) | Wed (3/05) | Thurs (3/06) | Fri (3/07) | Sat (3/08) |
| EX5 due @ 11:59 pm | | Final!! due @ 11:59 pm | | |
Mon (3/10) | Tues (3/11) | Wed (3/12) | Thurs (3/13) | Fri (3/14) | Sat (3/15) |
| EX6 due @ 11:59 pm | | | P4 due @ 11:59 pm | |
Microteach: Dynamic Programming
Min Cost to Climb Stairs
You are given an integer array cost where cost[i] is the cost of ith step on a staircase. Once you pay the cost, you can either climb one or two steps.
You can either start from the step with index 0, or the step with index 1.
Return the minimum cost to reach the top of the floor.
Example:
Input: cost = [1,100,1,1,1,100,1,1,100,1]
Output: 6
Explanation: You will start at index 0.
- Pay 1 and climb two steps to reach index 2.
- Pay 1 and climb two steps to reach index 4.
- Pay 1 and climb two steps to reach index 6.
- Pay 1 and climb one step to reach index 7.
- Pay 1 and climb two steps to reach index 9.
- Pay 1 and climb one step to reach the top.
The total cost is 6.
How would we do this currently? Recursion
Lets say minimumCost(i) represents the minimum cost to reach the ith step.
The base cases for this function will be
minimumCost(0) = minimumCost(1) = 0
since we are allowed to start on either step 0 or step 1.
For any other step i, we can write this as a recurrence: (Fibonacci from lect.)
minimumCost(i) = min(cost[i - 1] + minimumCost(i - 1),
cost[i - 2] + minimumCost(i - 2))
T(i) = T(i - 1) + T(i - 2)
Why is this inefficient?
To figure out what the value of minimumCost(5), we call minimumCost(3) and minimumCost(4).
minimumCost(4) then calls minimumCost(3) again.
Both the minimumCost(3) calls will call minimumCost(2), on top of another minimumCost(2) call from minimumCost(4).
Thats a LOT of repeated calculations!
The fix!
Dynamic Programming!!
Two different kinds of DP:
Top-Down
Take a recursive solution, figure out what solutions should be “saved”, and use those instead of calling the function again when they exist!
For example, if we calculate minimumCost(3) once, why should we calculate it again? Instead of going through the entire subtree every time we want to calculate minimumCost(3), we will store the value of minimumCost(3) after calculating it the first time, and refer to that instead.
This is what memoization is - caching "expensive" function calls to avoid duplicate computations.
We can use a hash map for the memoization, where each key will have the value minimumCost(key).
Top-Down Continued
Algorithm
Top-Down
class Solution {
private HashMap<Integer, Integer> memo = new HashMap<Integer, Integer>();
public int minCostClimbingStairs(int[] cost) {
return minimumCost(cost.length, cost);
}
private int minimumCost(int i, int[] cost) {
// Base case, we are allowed to start at either step 0 or step 1
if (i <= 1) {
return 0;
}
// Check if we have already calculated minimumCost(i)
if (memo.containsKey(i)) {
return memo.get(i);
}
// If not, cache the result in our hash map and return it
int downOne = cost[i - 1] + minimumCost(i - 1, cost);
int downTwo = cost[i - 2] + minimumCost(i - 2, cost);
memo.put(i, Math.min(downOne, downTwo));
return memo.get(i);
}
}
Define a hash map memo, where memo[i] represents the minimum cost of reaching the ith step.
Define a function minimumCost, where minimumCost(i) will determine the minimum cost to reach the ith step.
In our function minimumCost, first check the base cases: return 0 when i == 0 or i == 1.
Check if the argument i has already been calculated and stored in our hash map memo. If so, return memo[i].
If we already have the value, we won’t recalculate it!
Otherwise, use the recurrence relation to calculate memo[i], and then return memo[i].
Bottom-Up
What is Bottom-Up dynamic programming?
Bottom-Up approach
Let’s go back to the recurrence relation we found earlier:
Lets say minimumCost(i) represents the minimum cost to reach the ith step.
The base cases for this function will be
minimumCost(0) = minimumCost(1) = 0
since we are allowed to start on either step 0 or step 1.
For any other step i, we can write this as a recurrence:
minimumCost(i) = min(cost[i - 1] + minimumCost(i - 1),
cost[i - 2] + minimumCost(i - 2))
T(i) = T(i - 1) + T(i - 2)
Bottom-Up
What do we notice?
As you can see, we get the solution for the ith step by using solutions from earlier steps (just like in top-down dp!)
When does the sequence terminate?
Bottom-Up Algorithm
Bottom-Up Code
class Solution {
public int minCostClimbingStairs(int[] cost) {
// The array's length should be 1 longer than the length of cost
// This is because we can treat the "top floor" as a step to reach
int minimumCost[] = new int[cost.length + 1];
// Start iteration from step 2, since the minimum cost of reaching
// step 0 and step 1 is 0
for (int i = 2; i < minimumCost.length; i++) {
int takeOneStep = minimumCost[i - 1] + cost[i - 1];
int takeTwoSteps = minimumCost[i - 2] + cost[i - 2];
minimumCost[i] = Math.min(takeOneStep, takeTwoSteps);
}
// The final element in minimumCost refers to the top floor
return minimumCost[minimumCost.length - 1];
}
}
Define an array minimumCost, where minimumCost[i] represents the minimum cost of reaching the ith step. The array should be one element longer than costs (the “top” floor is one more than the cost of the steps to get there)
Iterate over the array starting at the 2nd index, as we know the minimum cost to reach step 0 and 1 is 0
For each step, apply the recurrence relation - minimumCost[i] = min(minimumCost[i - 1] + cost[i - 1], minimumCost[i - 2] + cost[i - 2]).
At the end, return the final element of minimumCost. Remember, we are treating this "step" as the top floor that we need to reach.
Difference between bottom-up and top-down
If you’ll notice, they’re quite similar! It depends how your brain works and what makes more sense to you.
Both are equally good ways of solving a DP problem.
Ed: Coin Change
Coin Change
Coin Change Recurrence Intuition
Let’s look at an example:
coins = [1, 2, 3]
amount = 6
For a recurrence, we’re going to take the BOTTOM-UP approach in order to solve this: i.e. breaking our larger problem into smaller subproblems!
i.e. what’s the min amount of coins for amount 5? What about amount 4? Or 3?
Recurrence cont.
Let’s define these “subproblem” answers using F(i), where F(i) is the min number of coins for amount i, where i is between 0 and the amount given.
We have an example here:
Coin Change Cont.
What pattern do we notice?
Coin Change Cont.
Let’s put this pattern into a recurrence:
Coin Change Cont.
Given our recurrence: