Fibonacci Using DP in DSA Rust - Time & Space Complexity
We want to understand how fast the Fibonacci number calculation using dynamic programming runs.
How does the time needed change as the input number grows?
Analyze the time complexity of the following code snippet.
fn fibonacci(n: usize) -> usize {
let mut dp = vec![0; n + 1];
dp[0] = 0;
dp[1] = 1;
for i in 2..=n {
dp[i] = dp[i - 1] + dp[i - 2];
}
dp[n]
}
This code calculates the nth Fibonacci number by storing previous results to avoid repeated work.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The for-loop that runs from 2 to n, calculating each Fibonacci number once.
- How many times: Exactly n - 1 times, once for each number from 2 up to n.
Each new Fibonacci number requires one addition operation, so the total work grows steadily with n.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 9 additions |
| 100 | About 99 additions |
| 1000 | About 999 additions |
Pattern observation: The number of operations grows roughly in a straight line as n increases.
Time Complexity: O(n)
This means the time to compute the nth Fibonacci number grows directly in proportion to n.
[X] Wrong: "Calculating Fibonacci with recursion is always fast because it just calls itself."
[OK] Correct: Simple recursion repeats many calculations, making it much slower than this dynamic programming approach.
Understanding how dynamic programming improves speed by remembering past results is a key skill that shows you can optimize solutions thoughtfully.
"What if we changed the code to use recursion with memoization instead of a loop? How would the time complexity change?"
Practice
Solution
Step 1: Understand recursive Fibonacci inefficiency
Simple recursion recalculates the same Fibonacci values many times, causing exponential time complexity.Step 2: How DP improves calculation
DP stores intermediate Fibonacci values in a table (memoization or tabulation), so each value is computed once.Final Answer:
It avoids repeated calculations by storing intermediate results. -> Option AQuick Check:
DP stores results to avoid repeats [OK]
- Thinking DP uses no extra memory
- Believing DP always runs in constant time
- Confusing recursion with DP
Solution
Step 1: Check vector size for DP storage
We need a vector of size n+1 to store Fibonacci numbers from 0 to n.Step 2: Verify mutability and initialization
Vector must be mutable to update values; initialized with zeros for all positions.Final Answer:
let mut fib = vec![0; n+1]; -> Option BQuick Check:
Mutable vector with n+1 zeros [OK]
- Using immutable vector when updates needed
- Incorrect vector size causing index errors
- Using fixed-size array without mutability
fn fib(n: usize) -> usize {
let mut dp = vec![0; n+1];
dp[1] = 1;
for i in 2..=n {
dp[i] = dp[i-1] + dp[i-2];
}
dp[n]
}
fn main() {
println!("{}", fib(5));
}Solution
Step 1: Trace DP array filling for n=5
dp[0]=0, dp[1]=1; then dp[2]=1, dp[3]=2, dp[4]=3, dp[5]=5.Step 2: Return dp[5]
Function returns dp[5], which is 5.Final Answer:
5 -> Option CQuick Check:
Fibonacci(5) = 5 [OK]
- Confusing index starting points
- Off-by-one errors in loop range
- Misreading dp array values
fn fib(n: usize) -> usize {
let mut dp = vec![0; n];
dp[0] = 0;
dp[1] = 1;
for i in 2..=n {
dp[i] = dp[i-1] + dp[i-2];
}
dp[n]
}Solution
Step 1: Check vector size and indexing
Vector size is n (indices 0 to n-1), but loop 2..=n and dp[n] access index n, causing out of bounds panic.Step 2: Fix vector size
Change to vec![0; n+1] to store values up to index n.Final Answer:
Change vector size to n+1 and return dp[n] -> Option AQuick Check:
Size n+1 for indices 0 to n [OK]
- Accessing dp[n] causing panic
- Incorrect vector size for indexing
- Ignoring zero-based indexing
fn fib_memo(n: usize, memo: &mut Vec) -> usize {
if n <= 1 {
return n;
}
if let Some(val) = memo[n] {
return val;
}
let result = fib_memo(n-1, memo) + fib_memo(n-2, memo);
memo[n] = Some(result);
result
}
fn main() {
let n = 10;
let mut memo = vec![None; n+1];
println!("{}", fib_memo(n, &mut memo));
}Solution
Step 1: Check memo vector type and initialization
Memo uses Option<usize> to track computed values; initialized with None for all indices.Step 2: Verify memoization logic
Function returns stored value if present; otherwise computes recursively and stores result.Step 3: Confirm base case correctness
Returns n for n=0 or 1, which matches Fibonacci definition.Final Answer:
Correct implementation of top-down DP with memoization -> Option DQuick Check:
Memo stores Option, uses recursion with caching [OK]
- Using usize vector without Option causes wrong caching
- Wrong base case returns
- Not passing memo as mutable reference
Ready to prove it?
Pass the test and this pattern moves unknown
