Memoization Top Down DP in DSA Go - Time & Space Complexity
We analyze how memoization in top-down dynamic programming speeds up repeated calculations.
We want to know how the number of operations changes as input size grows.
Analyze the time complexity of the following code snippet.
package main
import "fmt"
func fibMemo(n int, memo map[int]int) int {
if n <= 1 {
return n
}
if val, ok := memo[n]; ok {
return val
}
memo[n] = fibMemo(n-1, memo) + fibMemo(n-2, memo)
return memo[n]
}
func main() {
memo := make(map[int]int)
fmt.Println(fibMemo(10, memo))
}
This code calculates the nth Fibonacci number using memoization to avoid repeated work.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls to fibMemo with memo checks.
- How many times: Each unique input n is computed once; repeated calls return stored results immediately.
Each number from 0 up to n is computed once, so operations grow roughly in a straight line with n.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 computations |
| 100 | About 100 computations |
| 1000 | About 1000 computations |
Pattern observation: The work grows linearly as input size increases.
Time Complexity: O(n)
This means the time taken grows in direct proportion to the input size n.
[X] Wrong: "Memoization still does all recursive calls, so it's exponential time like plain recursion."
[OK] Correct: Memoization stores results so each input is computed once, avoiding repeated work and reducing time drastically.
Understanding memoization's time savings shows you can optimize recursive solutions, a key skill in many coding challenges.
"What if we removed memoization and used plain recursion? How would the time complexity change?"
Practice
Solution
Step 1: Understand memoization concept
Memoization saves the results of subproblems so they are not recalculated multiple times.Step 2: Compare options
Only To store results of subproblems to avoid repeated calculations correctly describes this purpose; others describe unrelated actions.Final Answer:
To store results of subproblems to avoid repeated calculations -> Option DQuick Check:
Memoization = Store subproblem results [OK]
- Confusing memoization with sorting
- Thinking memoization changes recursion to iteration
- Believing memoization increases input size
Solution
Step 1: Identify correct map initialization
In Go, a map from int to int is created with make(map[int]int).Step 2: Check other options
memo := []int{} is a slice, not a map; C uses wrong types; D creates a pointer to int, not a map.Final Answer:
memo := make(map[int]int) -> Option CQuick Check:
Map[int]int = make(map[int]int) [OK]
- Using slice instead of map for memo
- Wrong key or value types in map
- Using new(int) which is a pointer, not a map
package main
import "fmt"
var memo = make(map[int]int)
func fib(n int) int {
if n <= 1 {
return n
}
if val, ok := memo[n]; ok {
return val
}
memo[n] = fib(n-1) + fib(n-2)
return memo[n]
}
func main() {
fmt.Println(fib(5))
}Solution
Step 1: Trace fib(5) calls with memoization
fib(5) = fib(4) + fib(3), fib(4) = fib(3) + fib(2), fib(3) = fib(2) + fib(1), etc. Memo stores results to avoid repeats.Step 2: Calculate fib(5)
fib(0)=0, fib(1)=1, fib(2)=1, fib(3)=2, fib(4)=3, fib(5)=5.Final Answer:
5 -> Option AQuick Check:
fib(5) = 5 [OK]
- Confusing fib(5) with fib(6)
- Ignoring memo and recalculating
- Expecting runtime error due to recursion
func fib(n int, memo map[int]int) int {
if n <= 1 {
return n
}
if val, ok := memo[n]; ok {
return val
}
memo[n] = fib(n-1, memo) + fib(n-2, memo)
return memo[n]
}
func main() {
fmt.Println(fib(5, nil))
}Solution
Step 1: Analyze memo usage in fib call
fib is called with memo = nil, so accessing memo[n] causes runtime panic.Step 2: Fix by initializing memo map before first call
Pass a properly initialized map, e.g., make(map[int]int), to avoid nil map access.Final Answer:
Initialize memo map before passing to fib -> Option BQuick Check:
Nil map causes panic; initialize before use [OK]
- Passing nil map causing runtime panic
- Changing base case incorrectly
- Removing memo parameter loses memoization
countWays(4, memo)?
package main
import "fmt"
func countWays(n int, memo map[int]int) int {
if n == 0 {
return 1
}
if n < 0 {
return 0
}
if val, ok := memo[n]; ok {
return val
}
memo[n] = countWays(n-1, memo) + countWays(n-2, memo)
return memo[n]
}
func main() {
memo := make(map[int]int)
fmt.Println(countWays(4, memo))
}
Solution
Step 1: Understand problem and base cases
countWays(0) = 1 (one way to stand still), countWays(n<0) = 0 (no way). Ways to climb n steps = sum of ways for n-1 and n-2.Step 2: Calculate countWays(4)
countWays(1)=1, countWays(2)=2, countWays(3)=3, countWays(4)=5.Final Answer:
5 -> Option AQuick Check:
Ways to climb 4 steps = 5 [OK]
- Confusing base cases causing wrong counts
- Forgetting to initialize memo map
- Expecting infinite recursion without memo
Ready to prove it?
Pass the test and this pattern moves unknown
