1052-fibonacci-dp — Fibonacci Bottom-Up DP
Tutorial
The Problem
Bottom-up dynamic programming fills a table iteratively from the smallest sub-problems to the largest, avoiding recursion stack overhead and the overhead of hash map lookups in top-down memoization. For Fibonacci, this means filling an array from index 0 upward.
The classic space optimization reduces the O(n) array to O(1) space by observing that fib(n) depends only on the two previous values — keeping just two variables suffices.
🎯 Learning Outcomes
Vec-based DP table (O(n) space)Code Example
#![allow(clippy::all)]
// 1052: Fibonacci Bottom-Up DP with O(1) Space
// Approach 1: Vec-based bottom-up DP
fn fib_vec(n: usize) -> u64 {
if n <= 1 {
return n as u64;
}
let mut dp = vec![0u64; n + 1];
dp[1] = 1;
for i in 2..=n {
dp[i] = dp[i - 1] + dp[i - 2];
}
dp[n]
}
// Approach 2: O(1) space — two variables
fn fib_const(n: usize) -> u64 {
if n <= 1 {
return n as u64;
}
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let t = a + b;
a = b;
b = t;
}
b
}
// Approach 3: Iterator/fold approach
fn fib_fold(n: usize) -> u64 {
if n <= 1 {
return n as u64;
}
let (_, b) = (2..=n).fold((0u64, 1u64), |(a, b), _| (b, a + b));
b
}
#[cfg(test)]
mod tests {
use super::*;
const CASES: &[(usize, u64)] = &[
(0, 0),
(1, 1),
(2, 1),
(5, 5),
(10, 55),
(20, 6765),
(30, 832040),
];
#[test]
fn test_fib_vec() {
for &(n, expected) in CASES {
assert_eq!(fib_vec(n), expected, "fib_vec({n})");
}
}
#[test]
fn test_fib_const() {
for &(n, expected) in CASES {
assert_eq!(fib_const(n), expected, "fib_const({n})");
}
}
#[test]
fn test_fib_fold() {
for &(n, expected) in CASES {
assert_eq!(fib_fold(n), expected, "fib_fold({n})");
}
}
}Key Differences
fib is idiomatic and stack-safe; Rust's iterative version achieves the same without tail-call optimization (which Rust does not guarantee).fold((0, 1), |(a, b), _| (b, a+b)) is a compact functional expression; OCaml's equivalent is a tail-recursive fold.u64 panics on overflow in debug mode; OCaml's int wraps silently. Use u64::checked_add for safe overflow detection in Rust.OCaml Approach
OCaml's bottom-up Fibonacci:
let fib_dp n =
if n <= 1 then n
else
let arr = Array.make (n + 1) 0 in
arr.(1) <- 1;
for i = 2 to n do
arr.(i) <- arr.(i-1) + arr.(i-2)
done;
arr.(n)
(* O(1) space version *)
let fib n =
let rec go a b = function
| 0 -> a
| n -> go b (a + b) (n - 1)
in
go 0 1 n
OCaml's tail-recursive version is naturally O(1) space and stack-safe. Rust's iterative version avoids stack overflow for the same reason.
Full Source
#![allow(clippy::all)]
// 1052: Fibonacci Bottom-Up DP with O(1) Space
// Approach 1: Vec-based bottom-up DP
fn fib_vec(n: usize) -> u64 {
if n <= 1 {
return n as u64;
}
let mut dp = vec![0u64; n + 1];
dp[1] = 1;
for i in 2..=n {
dp[i] = dp[i - 1] + dp[i - 2];
}
dp[n]
}
// Approach 2: O(1) space — two variables
fn fib_const(n: usize) -> u64 {
if n <= 1 {
return n as u64;
}
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let t = a + b;
a = b;
b = t;
}
b
}
// Approach 3: Iterator/fold approach
fn fib_fold(n: usize) -> u64 {
if n <= 1 {
return n as u64;
}
let (_, b) = (2..=n).fold((0u64, 1u64), |(a, b), _| (b, a + b));
b
}
#[cfg(test)]
mod tests {
use super::*;
const CASES: &[(usize, u64)] = &[
(0, 0),
(1, 1),
(2, 1),
(5, 5),
(10, 55),
(20, 6765),
(30, 832040),
];
#[test]
fn test_fib_vec() {
for &(n, expected) in CASES {
assert_eq!(fib_vec(n), expected, "fib_vec({n})");
}
}
#[test]
fn test_fib_const() {
for &(n, expected) in CASES {
assert_eq!(fib_const(n), expected, "fib_const({n})");
}
}
#[test]
fn test_fib_fold() {
for &(n, expected) in CASES {
assert_eq!(fib_fold(n), expected, "fib_fold({n})");
}
}
}#[cfg(test)]
mod tests {
use super::*;
const CASES: &[(usize, u64)] = &[
(0, 0),
(1, 1),
(2, 1),
(5, 5),
(10, 55),
(20, 6765),
(30, 832040),
];
#[test]
fn test_fib_vec() {
for &(n, expected) in CASES {
assert_eq!(fib_vec(n), expected, "fib_vec({n})");
}
}
#[test]
fn test_fib_const() {
for &(n, expected) in CASES {
assert_eq!(fib_const(n), expected, "fib_const({n})");
}
}
#[test]
fn test_fib_fold() {
for &(n, expected) in CASES {
assert_eq!(fib_fold(n), expected, "fib_fold({n})");
}
}
}
Deep Comparison
Fibonacci Bottom-Up DP — Comparison
Core Insight
Bottom-up DP eliminates recursion overhead. The O(1) space variant using tuple state (a, b) → (b, a+b) is naturally expressed as a fold in both languages, showing how functional patterns align with space-optimal DP.
OCaml Approach
for loop and mutable arrayref cells for the two-variable version (imperative in functional clothing)List.fold_left with tuple destructuring for the pure functional versionList.init creates the iteration rangeRust Approach
Vec-based DP table — explicit allocation, indexed accesslet (mut a, mut b) for O(1) space(2..=n).fold((0, 1), |(a, b), _| (b, a+b)) — idiomatic iterator chainComparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Array DP | Array.make n 0 | vec![0u64; n + 1] |
| Mutable vars | ref cells + := | let mut + = |
| Fold syntax | List.fold_left (fun (a,b) _ -> ...) | (2..=n).fold((0,1), \|(a,b), _\| ...) |
| Range creation | List.init (n-1) Fun.id (allocates list) | 2..=n (zero-cost iterator) |
| Space complexity | Same O(1) for both | Same O(1) for both |
| Overflow handling | Silent overflow (OCaml ints) | Panic on debug, wrap on release |
Exercises
fib_matrix(n: u64) -> u64 using matrix exponentiation for O(log n) time complexity.fib_fold into a linear_recurrence(coeffs: &[i64], init: &[i64], n: usize) -> i64 for any linear recurrence.fib_pairs(n: usize) -> Vec<(u64, u64)> that returns all (fib(i), fib(i+1)) pairs up to n using the rolling-variable approach.