-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathDynamic_Programming_Tutorial.txt
195 lines (145 loc) · 9.01 KB
/
Dynamic_Programming_Tutorial.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
Dynamic Programming
===================
- Introduction:
===============
Dynamic programming is a method used in computer science and mathematics to solve complex problems by breaking them
down into simpler subproblems. It is particularly useful for optimization problems where the solution can be recursively
defined in terms of solutions to smaller instances of the same problem.
The main idea behind dynamic programming is to store the results of subproblems to avoid redundant computations.
This technique is known as "memoization" when done top-down (recursively) and "tabulation" when done bottom-up (iteratively).
1. Key Concepts in Dynamic Programming:
---------------------------------------
1. Optimal Substructure:
- A problem exhibits optimal substructure if an optimal solution to the problem can be constructed from optimal solutions
to its subproblems.
2. Overlapping Subproblems:
- A problem has overlapping subproblems if the same subproblems are solved multiple times during the computation of the
overall problem.
2. Steps to Apply Dynamic Programming:
--------------------------------------
1. Characterize the Structure of an Optimal Solution:
- Understand how to construct the optimal solution using solutions to subproblems.
2. Define the Value of an Optimal Solution Recursively:
- Formulate the problem recursively and identify the base cases.
3. Compute the Value of an Optimal Solution (Memoization or Tabulation):
- Use memoization to store results of subproblems to avoid recomputation (top-down approach).
- Use tabulation to build a table in a bottom-up manner, filling entries iteratively.
4. Construct the Optimal Solution (if needed):
- Trace back the stored results to construct the actual solution.
3. Examples of Dynamic Programming:
-----------------------------------
1. Fibonacci Sequence:
- The Fibonacci sequence can be defined as F(n) = F(n-1) + F(n-2) with base cases F(0) = 0 and F(1) = 1.
- Using dynamic programming, we can store the results of F(n-1) and F(n-2) to compute F(n) efficiently.
2. Knapsack Problem:
- Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so
that the total weight is less than or equal to a given limit and the total value is as large as possible.
- Dynamic programming can be used to build a table where each entry represents the maximum value that can be achieved
with a certain weight limit.
3. Longest Common Subsequence (LCS):
- Given two sequences, find the length of the longest subsequence present in both sequences.
- Dynamic programming can be used to fill a table where each entry L[i][j] represents the length of the LCS of the
first i elements of one sequence and the first j elements of the other.
4. Advantages of Dynamic Programming:
-------------------------------------
- Efficiency: By storing the results of subproblems, dynamic programming reduces the time complexity of solving complex problems.
- Optimization: It guarantees finding the optimal solution if the problem has optimal substructure and overlapping subproblems.
5. Disadvantages of Dynamic Programming:
----------------------------------------
- Space Complexity: Storing all subproblem results can require significant memory.
- Complexity in Implementation: Understanding and correctly applying dynamic programming can be challenging.
Dynamic programming is a powerful tool for solving a wide range of problems, particularly those involving optimization and
recursive substructure.
- Implementation Approach:
==========================
Dynamic programming can be implemented using two main approaches: top-down (memoization) and bottom-up (tabulation).
Both approaches aim to solve complex problems by breaking them down into simpler subproblems, but they differ in how they handle
the subproblems and store intermediate results.
1. Top-Down Approach (Memoization):
-----------------------------------
The top-down approach involves solving the problem recursively and storing the results of subproblems to avoid redundant computations.
This is known as memoization.
* Steps in Top-Down Approach:
-----------------------------
1. Recursive Formulation:
- Define the problem recursively in terms of smaller subproblems.
2. Memoization:
- Use a data structure (usually a dictionary or an array) to store the results of subproblems.
3. Check and Reuse:
- Before solving a subproblem, check if its result is already computed and stored. If so, reuse the stored result.
4. Base Cases:
- Define the base cases to stop the recursion.
* Example (Fibonacci Sequence):
-------------------------------
In the top-down approach, we use recursion and store the results of subproblems to avoid redundant computations.
This involves using a data structure like an array or a HashMap to store intermediate results.
import java.util.HashMap;
import java.util.Map;
public class FibonacciTopDown {
private Map<Integer, Integer> memo = new HashMap<>();
public int fib(int n) {
if (memo.containsKey(n)) {
return memo.get(n);
}
if (n <= 1) {
return n;
}
int result = fib(n - 1) + fib(n - 2);
memo.put(n, result);
return result;
}
public static void main(String[] args) {
FibonacciTopDown fibonacci = new FibonacciTopDown();
System.out.println(fibonacci.fib(10)); // Output: 55
}
}
2. Bottom-Up Approach (Tabulation):
-----------------------------------
The bottom-up approach involves solving the problem iteratively, starting from the smallest subproblems and building
up to the solution of the original problem. This is known as tabulation.
* Steps in Bottom-Up Approach:
------------------------------
1. Iterative Formulation:
- Define the problem iteratively, starting from the smallest subproblems.
2. Table Initialization:
- Use a data structure (usually an array) to store the results of subproblems.
3. Iterative Computation:
- Compute the results of subproblems in a specific order, usually from the smallest to the largest.
4. Final Solution:
- The final solution to the original problem is obtained from the last computed value in the table.
* Example (Fibonacci Sequence):
-------------------------------
In the bottom-up approach, we iteratively solve the problem from the smallest subproblems up to the original problem,
storing intermediate results in a data structure like an array.
public class FibonacciBottomUp {
public int fib(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];
}
public static void main(String[] args) {
FibonacciBottomUp fibonacci = new FibonacciBottomUp();
System.out.println(fibonacci.fib(10)); // Output: 55
}
}
* Comparison of Top-Down and Bottom-Up Approaches
- Top-Down (Memoization):
- Uses recursion and stores intermediate results.
- More intuitive for problems naturally expressed in recursive form.
- May lead to high recursion depth and potential stack overflow for very large problems.
- Can be easier to implement if the recursive solution is straightforward.
- Bottom-Up (Tabulation):
- Uses iteration and builds up solutions from smaller subproblems.
- Typically more space-efficient because it can often be optimized to use a fixed amount of space (e.g., only
storing the last two values for Fibonacci).
- Avoids recursion depth issues.
- Can be more complex to implement if the iterative solution is not obvious.
Both approaches ultimately achieve the same goal: solving the problem efficiently by leveraging the solutions to subproblems.
The choice between top-down and bottom-up depends on the specific problem and the programmer's preference or constraints.