Skip to content

Commit 31120e2

Browse files
authored
Create README.md
1 parent d07fd5a commit 31120e2

File tree

1 file changed

+249
-0
lines changed
  • 24 - Dynamic Programming Problems/01 - Example | Nth Fibonacci Number

1 file changed

+249
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<h1 align="center">Nth - Fibonacci - Number - Example</h1>
2+
3+
## Problem Statement
4+
**Problem URL :** [Nth Fibonacci Number](https://www.geeksforgeeks.org/problems/nth-fibonacci-number1335/1?itm_source=geeksforgeeks&itm_medium=article&itm_campaign=practice_card)
5+
6+
![image](https://github.com/user-attachments/assets/4f743224-0c5c-4b88-a02d-6cf06513ffa0)
7+
8+
## Problem Explanation
9+
The task is to find the Nth Fibonacci number. The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones, starting from 0 and 1. Therefore, the sequence starts: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, and so on.
10+
11+
You are required to write a function to compute the Nth Fibonacci number in this sequence.
12+
13+
### What is a Fibonacci Number?
14+
The Fibonacci sequence is defined as:
15+
- F(0) = 0
16+
- F(1) = 1
17+
- For n > 1, F(n) = F(n-1) + F(n-2)
18+
19+
This means that each number in the sequence is the sum of the two preceding ones.
20+
21+
### Calculation of Fibonacci Numbers:
22+
Let's break it down step-by-step.
23+
24+
#### Step 1: Base Cases
25+
There are two base cases in the Fibonacci sequence:
26+
- If N = 0, the Fibonacci number is 0.
27+
- If N = 1, the Fibonacci number is 1.
28+
29+
#### Step 2: Recursive Definition
30+
For any other number N (N > 1), the Fibonacci number is defined as the sum of the two preceding numbers:
31+
- F(N) = F(N-1) + F(N-2)
32+
33+
## What is Dynamic Programming
34+
Dynamic Programming (DP) is a problem-solving technique used to solve problems with overlapping subproblems and optimal substructure. It avoids redundant computations by storing the results of subproblems, making it more efficient than plain recursion.
35+
36+
### **Core Concepts of Dynamic Programming**
37+
38+
#### 1. **Overlapping Subproblems**
39+
Subproblems are solved multiple times during the computation.
40+
Example:
41+
Computing Fibonacci numbers recursively involves solving \( F(n-1) \) and \( F(n-2) \), but \( F(n-1) \) and \( F(n-2) \) have common subproblems like \( F(n-3) \), leading to redundant work.
42+
43+
#### 2. **Optimal Substructure**
44+
A problem exhibits optimal substructure if its solution can be derived from solutions of smaller subproblems.
45+
Example:
46+
The shortest path between two nodes in a graph can be computed by finding the shortest paths between intermediate nodes.
47+
48+
### **Three Key DP Techniques**
49+
50+
### **1. Top-Down Approach (Recursion + Memoization)**
51+
52+
The top-down approach involves solving problems recursively while storing results of already-computed subproblems in a cache or a table. This process is called **memoization**.
53+
54+
- **How It Works**:
55+
- Start solving the problem from the largest input.
56+
- Store the result of each subproblem in a memoization table.
57+
- Before solving a subproblem, check the table to see if it has already been solved.
58+
59+
- **Example: Fibonacci Sequence**
60+
The Fibonacci sequence is defined as:
61+
\( F(n) = F(n-1) + F(n-2) \), where \( F(0) = 0, F(1) = 1 \).
62+
63+
##### Source Code
64+
```cpp
65+
#include <vector>
66+
using namespace std;
67+
68+
class Solution {
69+
public:
70+
int fibonacci(int n, vector<int>& dp) {
71+
// Base cases
72+
if (n == 0 || n == 1) return n;
73+
74+
// If already computed, return the cached result
75+
if (dp[n] != -1) return dp[n];
76+
77+
// Compute the value recursively and store it in dp[n]
78+
dp[n] = fibonacci(n - 1, dp) + fibonacci(n - 2, dp);
79+
return dp[n];
80+
}
81+
82+
int nthFibonacci(int n) {
83+
vector<int> dp(n + 1, -1); // Initialize memoization table
84+
return fibonacci(n, dp);
85+
}
86+
};
87+
```
88+
89+
- **Advantages**: Efficient compared to plain recursion due to reduced recomputation.
90+
- **Disadvantages**: Uses extra space for the memoization table.
91+
92+
93+
94+
### **2. Bottom-Up Approach (Tabulation)**
95+
96+
The bottom-up approach involves solving all subproblems iteratively, starting from the smallest subproblems and building up to the desired solution. This is called **tabulation** because it uses a table to store intermediate results.
97+
98+
- **How It Works**:
99+
- Define a table (array or matrix) to store the results of subproblems.
100+
- Solve the smallest subproblem first, then use its result to solve larger subproblems.
101+
102+
- **Example: Fibonacci Sequence**
103+
104+
##### Source Code
105+
```cpp
106+
#include <vector>
107+
using namespace std;
108+
109+
class Solution {
110+
public:
111+
int nthFibonacci(int n) {
112+
vector<int> dp(n + 1); // Table to store Fibonacci numbers
113+
dp[0] = 0; // Base case
114+
dp[1] = 1; // Base case
115+
116+
for (int i = 2; i <= n; i++) {
117+
dp[i] = dp[i - 1] + dp[i - 2]; // Use previously computed values
118+
}
119+
120+
return dp[n];
121+
}
122+
};
123+
```
124+
125+
- **Advantages**: Avoids recursion and stack overflow.
126+
- **Disadvantages**: Requires space proportional to the size of the table.
127+
128+
129+
### **3. Space-Optimized Approach**
130+
131+
In many problems, only a few values from the table are required at any given time. We can optimize the space by storing only the necessary states.
132+
133+
- **How It Works**:
134+
- Reduce the size of the table by keeping only the last few values needed for the computation.
135+
136+
- **Example: Fibonacci Sequence**
137+
138+
##### Source Code
139+
```cpp
140+
class Solution {
141+
public:
142+
int nthFibonacci(int n) {
143+
if (n == 0) return 0; // Base case
144+
if (n == 1) return 1; // Base case
145+
146+
int a = 0, b = 1; // Store only the last two Fibonacci numbers
147+
int current;
148+
149+
for (int i = 2; i <= n; i++) {
150+
current = a + b; // Compute the current Fibonacci number
151+
a = b; // Update a to the previous b
152+
b = current; // Update b to the current value
153+
}
154+
155+
return current;
156+
}
157+
};
158+
```
159+
160+
- **Advantages**: Significantly reduces space complexity.
161+
- **Disadvantages**: Slightly harder to implement compared to the tabulation approach.
162+
163+
164+
165+
### **Comparison of Techniques**
166+
167+
| **Technique** | **Space Complexity** | **Time Complexity** | **Approach** |
168+
|-|-|-|-|
169+
| **Top-Down (Memoization)**| \( O(n) \) | \( O(n) \) | Recursive with caching |
170+
| **Bottom-Up (Tabulation)**| \( O(n) \) | \( O(n) \) | Iterative, builds solution step-by-step |
171+
| **Space-Optimized** | \( O(1) \) | \( O(n) \) | Iterative, stores only needed values |
172+
173+
174+
## Why Not Recursive Approach
175+
##### source code
176+
```cpp
177+
class Solution {
178+
public:
179+
int nthFibonacci(int n) {
180+
// Base case: If n is 0 or 1, return n directly.
181+
// F(0) = 0 and F(1) = 1 are the first two terms of the Fibonacci sequence.
182+
if (n == 0 || n == 1)
183+
return n;
184+
185+
// Recursive case:
186+
// The nth Fibonacci number is the sum of the (n-1)th and (n-2)th Fibonacci numbers.
187+
// Recursively calculate the values for smaller subproblems.
188+
return nthFibonacci(n - 1) + nthFibonacci(n - 2);
189+
}
190+
};
191+
```
192+
193+
The **recursive approach** for solving problems like the Fibonacci sequence may seem intuitive and elegant, but it has significant drawbacks that make it inefficient for larger inputs. Let’s break this down:
194+
195+
### **Reasons to Avoid the Recursive Approach**
196+
197+
#### 1. **Exponential Time Complexity**
198+
- The time complexity of the recursive Fibonacci solution is **O(2^n)**. This happens because each call to the function makes two additional calls, leading to an exponential growth in the number of calls.
199+
- Example: Calculating \( F(5) \) involves computing \( F(4) \) and \( F(3) \), but \( F(4) \) also computes \( F(3) \) and \( F(2) \), leading to redundant calculations.
200+
201+
#### 2. **Redundant Calculations**
202+
- The same subproblems are solved repeatedly. For example:
203+
- To compute \( F(5) \), you compute \( F(4) \) and \( F(3) \).
204+
- Computing \( F(4) \) requires \( F(3) \) and \( F(2) \), so \( F(3) \) is calculated twice.
205+
- This redundancy increases with \( n \), wasting computation time.
206+
207+
#### 3. **Stack Overflow (Memory Constraints)**
208+
- Recursive calls consume stack space proportional to the depth of the recursion. For Fibonacci, the depth of the recursion is \( n \), so the space complexity is **O(n)**.
209+
- For very large \( n \), this can lead to **stack overflow errors**.
210+
211+
#### 4. **Lack of Intermediate Storage**
212+
- In the recursive approach, no storage (memoization) is used to save the results of already-computed subproblems. This makes it inefficient compared to dynamic programming approaches.
213+
214+
215+
### **Comparison with Dynamic Programming**
216+
217+
| **Aspect** | **Recursive Approach** | **Dynamic Programming** |
218+
|----------------------|------------------------------|-------------------------------------|
219+
| **Time Complexity** | \( O(2^n) \) | \( O(n) \) |
220+
| **Space Complexity** | \( O(n) \) (due to the call stack) | \( O(n) \) (or \( O(1) \) if space optimized) |
221+
| **Redundancy** | High (repeated subproblems) | Avoided (results stored and reused) |
222+
| **Usability** | Simple but inefficient | Slightly complex but highly efficient |
223+
224+
225+
### **Example: Recursive Redundancy**
226+
To calculate \( F(5) \):
227+
228+
#### Recursive Tree:
229+
```
230+
F(5)
231+
/ \
232+
F(4) F(3)
233+
/ \ / \
234+
F(3) F(2) F(2) F(1)
235+
/ \
236+
F(2) F(1)
237+
```
238+
239+
- **Redundancy**: \( F(3) \) is computed **twice**, \( F(2) \) is computed **three times**, and the redundancy increases exponentially as \( n \) grows.
240+
241+
242+
### **When Is Recursion Acceptable?**
243+
The recursive approach can be used for:
244+
- **Small inputs**: When \( n \) is small (e.g., \( n \leq 10 \)), the inefficiency is negligible.
245+
- **Educational purposes**: Useful for understanding the problem structure and basic recursion.
246+
- **Tree-like problems**: Where each recursive call generates unique subproblems without overlap.
247+
248+
### **Conclusion**
249+
The recursive approach for problems like Fibonacci is not practical for large inputs due to exponential time complexity and stack overflow issues. It is better to use **dynamic programming** with memoization (Top-Down) or tabulation (Bottom-Up) to solve these problems efficiently.

0 commit comments

Comments
 (0)