🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing the same states by caching results, drastically improving efficiency.
Intuition
Store results of recursive calls so that when the same step is reached again, return the cached result instead of recomputing.
Algorithm
- Create a memo dictionary to store min cost for each step.
- Modify recursive function to check memo before computing.
- Compute min cost recursively with memoization.
- Return min cost to reach top from last or second last step.
💡 Memoization converts exponential recursion into linear time by avoiding repeated work.
Recurrence:dp[i] = cost[i] + min(dp[i-1], dp[i-2])
def minCostClimbingStairs(cost):
n = len(cost)
memo = {}
def dfs(i):
if i < 0:
return 0
if i == 0 or i == 1:
return cost[i]
if i in memo:
return memo[i]
memo[i] = cost[i] + min(dfs(i-1), dfs(i-2))
return memo[i]
return min(dfs(n-1), dfs(n-2))
# Example usage
if __name__ == '__main__':
cost = [10, 15, 20]
print(minCostClimbingStairs(cost)) # Output: 15
Line Notes
memo = {}Initialize cache to store computed results
if i in memo:Return cached result if available to avoid recomputation
memo[i] = cost[i] + min(dfs(i-1), dfs(i-2))Compute and cache min cost for step i
return min(dfs(n-1), dfs(n-2))Return min cost to reach top
import java.util.HashMap;
public class Solution {
private HashMap<Integer, Integer> memo = new HashMap<>();
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
return Math.min(dfs(cost, n - 1), dfs(cost, n - 2));
}
private int dfs(int[] cost, int i) {
if (i < 0) return 0;
if (i == 0 || i == 1) return cost[i];
if (memo.containsKey(i)) return memo.get(i);
int res = cost[i] + Math.min(dfs(cost, i - 1), dfs(cost, i - 2));
memo.put(i, res);
return res;
}
public static void main(String[] args) {
Solution sol = new Solution();
int[] cost = {10, 15, 20};
System.out.println(sol.minCostClimbingStairs(cost)); // 15
}
}
Line Notes
private HashMap<Integer, Integer> memo = new HashMap<>();Cache to store computed results
if (memo.containsKey(i)) return memo.get(i);Return cached result if available
memo.put(i, res);Store computed result in cache
return Math.min(dfs(cost, n - 1), dfs(cost, n - 2));Return min cost to reach top
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
unordered_map<int,int> memo;
int dfs(const vector<int>& cost, int i) {
if (i < 0) return 0;
if (i == 0 || i == 1) return cost[i];
if (memo.count(i)) return memo[i];
memo[i] = cost[i] + min(dfs(cost, i - 1), dfs(cost, i - 2));
return memo[i];
}
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
memo.clear();
return min(dfs(cost, n - 1), dfs(cost, n - 2));
}
int main() {
vector<int> cost = {10, 15, 20};
cout << minCostClimbingStairs(cost) << endl; // 15
return 0;
}
Line Notes
unordered_map<int,int> memo;Cache to store computed results
if (memo.count(i)) return memo[i];Return cached result if available
memo[i] = cost[i] + min(dfs(cost, i - 1), dfs(cost, i - 2));Compute and cache min cost for step i
return min(dfs(cost, n - 1), dfs(cost, n - 2));Return min cost to reach top
function minCostClimbingStairs(cost) {
const n = cost.length;
const memo = new Map();
function dfs(i) {
if (i < 0) return 0;
if (i === 0 || i === 1) return cost[i];
if (memo.has(i)) return memo.get(i);
const res = cost[i] + Math.min(dfs(i - 1), dfs(i - 2));
memo.set(i, res);
return res;
}
return Math.min(dfs(n - 1), dfs(n - 2));
}
console.log(minCostClimbingStairs([10, 15, 20])); // 15
Line Notes
const memo = new Map();Cache to store computed results
if (memo.has(i)) return memo.get(i);Return cached result if available
memo.set(i, res);Store computed result in cache
return Math.min(dfs(n - 1), dfs(n - 2));Return min cost to reach top
TimeO(n)
SpaceO(n) for memo and recursion stack
Each step is computed once and cached, reducing exponential calls to linear.
💡 For n=20, this means about 20 calls, which is efficient.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for large inputs.