Tabulation Bottom Up DP in DSA Python - Time & Space Complexity
We want to understand how the time needed grows when using tabulation in bottom-up dynamic programming.
Specifically, how the number of steps changes as the input size increases.
Analyze the time complexity of the following code snippet.
def fib_tab(n: int) -> int:
dp = [0] * (n + 1)
if n > 0:
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
This code calculates the nth Fibonacci number using bottom-up tabulation.
- Primary operation: The for-loop that fills the dp array from 2 to n.
- How many times: It runs (n - 1) times, which grows as n grows.
Each step in the loop does a simple addition and assignment, repeated for each number up to n.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 9 additions and assignments |
| 100 | About 99 additions and assignments |
| 1000 | About 999 additions and assignments |
Pattern observation: The number of operations grows roughly in a straight line as n increases.
Time Complexity: O(n)
This means the time needed grows directly in proportion to the input size.
[X] Wrong: "Tabulation is always slower than recursion because it uses loops."
[OK] Correct: Tabulation avoids repeated work by storing results, so it actually runs faster than naive recursion which repeats calculations many times.
Understanding how tabulation scales helps you explain efficient solutions clearly and confidently in interviews.
"What if we changed the dp array to store results only for the last two numbers instead of all n? How would the time complexity change?"
Practice
Tabulation Bottom Up DP in dynamic programming?Solution
Step 1: Understand bottom-up approach
Tabulation means filling a table starting from the smallest subproblems, so we solve all smaller parts first.Step 2: Compare with other methods
Unlike recursion without storage, tabulation avoids repeated work by storing results in a table.Final Answer:
Build a table from the smallest subproblems up to the main problem -> Option DQuick Check:
Tabulation = Bottom-up table filling [OK]
- Confusing tabulation with recursion
- Thinking tabulation starts from the biggest problem
- Ignoring the use of a table to store results
Solution
Step 1: Understand Fibonacci base cases
Fibonacci starts with dp[0] = 0 and dp[1] = 1, so initialization must reflect that.Step 2: Check each option
dp = [0, 1] + [0] * (n - 2) correctly sets dp[0]=0, dp[1]=1, and fills the rest with zeros for further calculation.Final Answer:
dp = [0, 1] + [0] * (n - 2) -> Option AQuick Check:
Correct base cases = dp = [0, 1] + [0] * (n - 2) [OK]
- Setting wrong base values
- Using multiplication incorrectly for initialization
- Not allocating enough space for dp
def fib(n):
dp = [0, 1] + [0] * (n - 2)
for i in range(2, n):
dp[i] = dp[i-1] + dp[i-2]
return dp[n-1]
print(fib(6))Solution
Step 1: Trace the DP table filling
dp starts as [0,1,0,0,0,0]. Iterations fill dp as: dp[2]=1, dp[3]=2, dp[4]=3, dp[5]=5.Step 2: Return the last element dp[5]
dp[5] = 5, so fib(6) returns 5.Final Answer:
5 -> Option AQuick Check:
Fibonacci(6) = 5 [OK]
- Off-by-one errors in indexing
- Confusing dp[n-1] with dp[n]
- Incorrect loop range
def fib(n):
dp = [0] * n
dp[0] = 0
dp[1] = 1
for i in range(2, n):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
print(fib(5))Solution
Step 1: Identify index error in return statement
dp has indices 0 to n-1, so dp[n] is out of range causing error.Step 2: Fix by returning dp[n-1]
Returning dp[n-1] gives the last Fibonacci number correctly.Final Answer:
Change return dp[n] to return dp[n-1] -> Option CQuick Check:
Index error fixed by dp[n-1] return [OK]
- Returning dp[n] causing index error
- Wrong loop start index
- Incorrect base values
coins = [1, 2, 5] and an amount amount = 5, which tabulation code correctly computes the number of ways to make the amount using these coins?Solution
Step 1: Understand coin change tabulation
We initialize dp[0] = 1 (one way to make zero). For each coin, update dp[x] by adding dp[x-coin].Step 2: Check each option's correctness
dp = [1] + [0]*amount for coin in coins: for x in range(coin, amount+1): dp[x] += dp[x-coin] print(dp[amount]) correctly uses nested loops with dp[x] += dp[x-coin]. dp = [0]*(amount+1) dp[0] = 1 for x in range(1, amount+1): for coin in coins: if coin <= x: dp[x] += dp[x-coin] print(dp[amount]) loops coins inside amount, which counts duplicates. dp = [0]*(amount+1) dp[0] = 1 for coin in coins: for x in range(amount+1): if x >= coin: dp[x] += dp[x-coin] print(dp[amount]) loops x from 0 causing extra checks. dp = [0]*(amount+1) dp[0] = 1 for coin in coins: for x in range(coin, amount+1): dp[x] = dp[x-coin] print(dp[amount]) overwrites dp[x] instead of adding.Final Answer:
dp = [1] + [0]*amount for coin in coins: for x in range(coin, amount+1): dp[x] += dp[x-coin] print(dp[amount]) -> Option BQuick Check:
Correct coin change tabulation = dp = [1] + [0]*amount for coin in coins: for x in range(coin, amount+1): dp[x] += dp[x-coin] print(dp[amount]) [OK]
- Overwriting dp[x] instead of adding
- Wrong loop order causing duplicates
- Not initializing dp[0] = 1
Ready to prove it?
Pass the test and this pattern moves unknown
