Chapter 52
HomeThe bottom-up Fibonacci uses an array of size N+1. But we only ever need the last two values. By keeping only what's necessary, we go from O(N) memory to O(1). This pattern — "keep the sliding window" — applies to almost every linear DP.
int fib(int n) {
if (n <= 1) return n;
vector dp(n + 1); // ← stores EVERYTHING
dp[0] = 0; dp[1] = 1;
for (int i = 2; i <= n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n];
}
// n = 10⁷: dp array = 40 MB (just for fibonacci!)
🚨 Memory waste
At any point, we only need dp[i-1] and dp[i-2] to
compute dp[i]. The entire history is irrelevant. Storing all N
values is wasteful and limits input size.
int fib(int n) {
if (n <= 1) return n;
int a = 0, b = 1; // dp[i-2], dp[i-1]
for (int i = 2; i <= n; i++) {
int c = a + b; // dp[i]
a = b;
b = c;
}
return b;
}
// n = 10⁷: 3 int variables = 12 bytes
fn fib(n: u64) -> u64 {
if n <= 1 { return n; }
let (mut a, mut b) = (0, 1);
for _ in 2..=n {
let c = a + b;
a = b;
b = c;
}
b
}
💡 The rolling variable pattern
dp[i] = f(dp[i-1], dp[i-2], ..., dp[i-k]) — if the recurrence
only looks back K steps, you only need K variables. This is called "rolling
DP" or "space-optimised DP." Always ask: "do I really need the whole table?"
dp[i] only needs the last K values, keep K variables, not the full table.