Fibonacci Numbers (Dynamic Programming)
📊 Fibonacci Numbers - Dynamic Programming
DP Array (Building F(5)):
Space-Optimized (O(1) space):
Step 1: Base case: F(0) = 0
Progress: 1 / 6
Pattern:
Each Fibonacci number = sum of previous two. DP stores results to avoid recomputation. Space-optimized version only keeps last 2 values!
🎯 Explain Like I'm 5...
Imagine climbing stairs 🪜 but you're special - you can take 1 step OR 2 steps at a time! How many ways can you reach the top?
🪜 The Magic Stairs Pattern:
- • Stair 0: 1 way (don't move!)
- • Stair 1: 1 way (take 1 step)
- • Stair 2: 2 ways (1+1 steps OR take one big 2-step!)
- • Stair 3: 3 ways (1+1+1 OR 1+2 OR 2+1)
- • Stair 4: 5 ways (1+1+1+1 OR 1+1+2 OR 1+2+1 OR 2+1+1 OR 2+2)
🔍 Notice the pattern: 1, 1, 2, 3, 5... Each number is the sum of previous TWO numbers!
Ways to reach step 4 = (ways to reach step 3) + (ways to reach step 2) = 3 + 2 = 5 ✨
🚀 The Secret: You can get to ANY step from the previous step (1 step back) OR from 2 steps back! So just ADD those two numbers! It's like each number is made from combining its two neighbors! 🧮
When Should You Use This Pattern?
- ✓Answer depends on previous answers Counting ways
- ✓to reach a state
- ✓Staircase, house robber style problems
- ✓Can break problem into overlapping subproblems
📝 Example 1: Fibonacci Number
Calculate the nth Fibonacci number efficiently.
Evolution of Solutions:
- Naive recursion: O(2^n) - very slow!
- Memoization (top-down DP): O(n) time, O(n) space
- Tabulation (bottom-up DP): O(n) time, O(n) space
- Space optimized: O(n) time, O(1) space!
import java.util.*;public class Fibonacci { // Approach 1: Naive recursion (SLOW - O(2^n)) public static int fibRecursive(int n) { if (n <= 1) return n; return fibRecursive(n - 1) + fibRecursive(n - 2); } // Approach 2: Memoization (Top-down DP) public static int fibMemo(int n) { Map<Integer, Integer> memo = new HashMap<>(); return fibMemoHelper(n, memo); } private static int fibMemoHelper(int n, Map<Integer, Integer> memo) { if (n <= 1) return n; if (memo.containsKey(n)) return memo.get(n); int result = fibMemoHelper(n - 1, memo) + fibMemoHelper(n - 2, memo); memo.put(n, result); return result; } // Approach 3: Tabulation (Bottom-up DP) public static int fibTabulation(int n) { if (n <= 1) return n; int[] dp = new int[n + 1]; dp[0] = 0; dp[1] = 1; for (int i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } // Approach 4: Space optimized (Best!) public static int fibOptimized(int n) { if (n <= 1) return n; int prev2 = 0; int prev1 = 1; int current = 0; for (int i = 2; i <= n; i++) { current = prev1 + prev2; prev2 = prev1; prev1 = current; } return current; } public static void main(String[] args) { int n = 10; System.out.println("Fib(" + n + ") recursive: " + fibRecursive(n)); System.out.println("Fib(" + n + ") memo: " + fibMemo(n)); System.out.println("Fib(" + n + ") tabulation: " + fibTabulation(n)); System.out.println("Fib(" + n + ") optimized: " + fibOptimized(n)); // All output: 55 }}📝 Example 2: Climbing Stairs
Count the number of ways to climb n stairs if you can take 1 or 2 steps at a time.
public class ClimbingStairs { // This is exactly Fibonacci! // To reach step n, you came from step n-1 or n-2 // ways(n) = ways(n-1) + ways(n-2) public static int climbStairs(int n) { if (n <= 2) return n; int prev2 = 1; // ways to reach step 1 int prev1 = 2; // ways to reach step 2 int current = 0; for (int i = 3; i <= n; i++) { current = prev1 + prev2; prev2 = prev1; prev1 = current; } return current; } // With k steps allowed (more general) public static int climbStairsK(int n, int k) { int[] dp = new int[n + 1]; dp[0] = 1; // One way to stay at ground for (int i = 1; i <= n; i++) { // Sum all possible previous steps (1 to k steps back) for (int step = 1; step <= k && step <= i; step++) { dp[i] += dp[i - step]; } } return dp[n]; } public static void main(String[] args) { int n = 5; System.out.println("Ways to climb " + n + " stairs: " + climbStairs(n)); // Output: 8 // [1+1+1+1+1, 1+1+1+2, 1+1+2+1, 1+2+1+1, 2+1+1+1, 1+2+2, 2+1+2, 2+2+1] System.out.println("Ways with 3 steps allowed: " + climbStairsK(n, 3)); // Output: 13 }}📝 Example 3: House Robber
Rob houses to maximize money, but can't rob two adjacent houses.
public class HouseRobber { // At each house: rob it (skip previous) or skip it (keep previous max) // dp[i] = max(nums[i] + dp[i-2], dp[i-1]) public static int rob(int[] nums) { if (nums.length == 0) return 0; if (nums.length == 1) return nums[0]; int prev2 = 0; int prev1 = nums[0]; int current = 0; for (int i = 1; i < nums.length; i++) { // Rob current house + max from 2 houses back // OR skip current, keep previous max current = Math.max(nums[i] + prev2, prev1); prev2 = prev1; prev1 = current; } return current; } // House Robber II: Houses in a circle (first and last are adjacent) public static int robCircular(int[] nums) { if (nums.length == 1) return nums[0]; // Case 1: Rob houses 0 to n-2 (exclude last) int max1 = robRange(nums, 0, nums.length - 2); // Case 2: Rob houses 1 to n-1 (exclude first) int max2 = robRange(nums, 1, nums.length - 1); return Math.max(max1, max2); } private static int robRange(int[] nums, int start, int end) { int prev2 = 0; int prev1 = 0; int current = 0; for (int i = start; i <= end; i++) { current = Math.max(nums[i] + prev2, prev1); prev2 = prev1; prev1 = current; } return current; } public static void main(String[] args) { int[] houses = {2, 7, 9, 3, 1}; System.out.println("Max money (linear): " + rob(houses)); // Output: 12 (rob houses 0, 2, 4: 2+9+1) int[] circularHouses = {2, 3, 2}; System.out.println("Max money (circular): " + robCircular(circularHouses)); // Output: 3 (can't rob both 0 and 2 since they're adjacent in circle) }}🔑 Key Points to Remember
- 1️⃣Pattern: dp[i] = f(dp[i-1], dp[i-2], ...)
- 2️⃣Always prefer space-optimized version (O(1) space)
- 3️⃣Many problems are disguised Fibonacci!
- 4️⃣Time: O(n), Space: O(1) with optimization
💪 Practice Problems
- • Min Cost Climbing Stairs
- • Delete and Earn
- • Decode Ways
- • Unique Paths
- • Jump Game