|
1 | | -# dynamic-programming |
| 1 | +### What is Dynamic programming? |
| 2 | + |
| 3 | +Dynamic programming is a computer programming method used to avoid computing |
| 4 | +multiple time the same subproblem in a recursive algorithm. |
| 5 | + |
| 6 | +Dynamic programming is applied to optimization problems where can be many possible solutions. |
| 7 | +Each solution has a value what can be find by solution with the optimal (minimum or maximum) value. |
| 8 | +This solution call an optimal solution to the problem. |
| 9 | + |
| 10 | +Dynamic programming method can design in 4 steps: |
| 11 | +1. Characterize the structure of an optimal solution. |
| 12 | +2. Recursively define the value of an optimal solution. |
| 13 | +3. Compute the value of an optimal solution in a bottom-up fashion. |
| 14 | +4. Construct an optimal solution from computed information. |
| 15 | + |
| 16 | +<b>*</b>Some of the alogirthm problem can be solved by the "divide and conquer" strategy instead of Dynamic programming. |
| 17 | +For example: |
| 18 | + - Merge sort |
| 19 | + - Quick sort |
| 20 | + |
| 21 | +Solutions for this algorithms not overlapping sub-problems, so they not classified as dynamic programming problems. |
| 22 | + |
| 23 | +### The Fibonacci |
| 24 | + |
| 25 | +Numbers( 0,1,1,2,3,5,8,13,21,34...), it’s the Fibonacci sequence, described by the recursive formula: |
| 26 | + |
| 27 | +``` |
| 28 | +
|
| 29 | +F(0) = 0, (1) = 1 |
| 30 | +F(N) = F(N - 1) + F(N - 2), for N > 1. |
| 31 | +
|
| 32 | +``` |
| 33 | + |
| 34 | +Fibonacci sequence which approximates the [golden spiral](https://en.wikipedia.org/wiki/Golden_ratio#Relationship_to_Fibonacci_sequence). |
| 35 | + |
| 36 | +The spiral is drawn starting from the inner 1×1 square and continues outwards to successively |
| 37 | + larger squares. |
| 38 | + |
| 39 | + |
| 40 | + |
| 41 | +Commonly denoted F(n) form a sequence, such that each number is the |
| 42 | +sum of the two preceding ones, starting from 0 and 1. |
| 43 | + |
| 44 | +Given N, calculate F(N). |
| 45 | +Fibonacci sequence most common example to Dynamic programming method. |
| 46 | + |
| 47 | +Trivial algorithm for computing F(N): |
| 48 | +``` |
| 49 | +
|
| 50 | +if n = 0: return 0 |
| 51 | +else if n = 1: return 1 |
| 52 | +else: return naive F(n − 1) + F(n − 2) |
| 53 | +``` |
| 54 | + |
| 55 | +JavaScript implementation: |
| 56 | + |
| 57 | +```js |
| 58 | + |
| 59 | +/** |
| 60 | + * Time complexity: O(2^n) |
| 61 | + * Space complexity: O(n) |
| 62 | + * |
| 63 | + * @param number N |
| 64 | + * @return number |
| 65 | + */ |
| 66 | +function getFibonacciNumberRecursive(N) { |
| 67 | + if (N <= 1) { |
| 68 | + return N; |
| 69 | + } |
| 70 | + return getFibonacciNumberRecursive(N - 1) + getFibonacciNumberRecursive(N - 2); |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +### Runtime Analysis: |
| 75 | + |
| 76 | +To calculate F(n) as sum of time to calculate F(n-1) and F(n-2), plus time to calculate them together O(1): |
| 77 | +``` |
| 78 | +
|
| 79 | +T(n<=1) = O(1) - for all operations F(1), F(0) |
| 80 | +
|
| 81 | +T(n) = T(n − 1) + T(n − 2) + O(1) |
| 82 | +T(n) = T(n − 1) + T(n − 2) + c |
| 83 | +2^kT(n − 2 * k) + c(2^(k−1) + 2^(k−2) + . . . + 2 + 1) = Ω(c2 ^ (n/2)) |
| 84 | +
|
| 85 | +``` |
| 86 | + |
| 87 | +Where "c" is the time needed to add n-bit numbers. Since |
| 88 | + |
| 89 | +``` |
| 90 | +T(n) = Ω(n2 ^ n/2) => T(n) = O(2^n) |
| 91 | +
|
| 92 | +``` |
| 93 | + |
| 94 | +<b>O(2^n)</b> - exponential time and O(n) space complexity for call stack size. |
| 95 | + |
| 96 | +To describe <b>O(2^n)</b> time complexity, lets draw the recursion tree of calls, |
| 97 | +which will have depth n and intuitively figure out that this function |
| 98 | +is asymptotically <b>O(2^n)</b>. |
| 99 | + |
| 100 | +To prove this conjecture by induction, let shows recursion tree for F(5) |
| 101 | + |
| 102 | +``` |
| 103 | +
|
| 104 | + /<------------- F(5) ------------------>\ |
| 105 | + / \ |
| 106 | + F(4)[F(n - 1)] F(3)[F(n - 2)] |
| 107 | + / \ / \ |
| 108 | + / \ / \ |
| 109 | + F(3)[F(n - 1)] F(2)[F(n - 2)] F(2)[F(n - 1)] F(1)[F(n - 2)] |
| 110 | + / \ / \ / \ |
| 111 | + / \ / \ / \ |
| 112 | + F(2)[F(n - 1)] F(2)[F(n - 1)] F(1)[F(n - 1)] F(0)[F(n - 2)] F(1)[F(n - 1)] F(0)[F(n - 2)] |
| 113 | + / \ |
| 114 | + / \ |
| 115 | + F(1)[F(n - 1)] F(0)[F(n - 2)] |
| 116 | +
|
| 117 | +``` |
| 118 | + |
| 119 | +In provide recursion tree of calls example, getFibonacciNumberRecursive(5) or F(5) function make |
| 120 | +multiple execution with same arguments: |
| 121 | +- F(2) - 4 times |
| 122 | +- F(3) - 2 times |
| 123 | +- The leaves of the recursion tree will always return 1 (F(1) and F(0)) |
| 124 | + |
| 125 | +Assume T(n-1) = O(2^(n-1)), therefore |
| 126 | + |
| 127 | +``` |
| 128 | +
|
| 129 | +T(n) = T(n-1) + T(n-2) + O(1) which is equal to |
| 130 | +T(n) = O(2 ^ (n-1)) + O(2 ^ (n-2)) + O(1) = O(2^n) |
| 131 | +
|
| 132 | +``` |
| 133 | + |
| 134 | +Consequently, the tight bound for this function is the Fibonacci sequence itself (~θ(1.6^n)) |
| 135 | +whitch related to Golden ratio |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | +### Improved Fibonacci Algorithm by memoization. |
| 140 | + |
| 141 | +Simple way to optimeze function getFibonacciNumberRecursive, create a chache Object for memoize storage. |
| 142 | +Runtime, assuming n-bit registers for each entry of memo data structure(cache Object): |
| 143 | + |
| 144 | +``` |
| 145 | +
|
| 146 | +T(n) = T(n − 1) + O(1) = O(n) |
| 147 | +T(n) = T(n − 1) + c = O(cn) |
| 148 | +T(n) = O(n ^ 2) |
| 149 | +``` |
| 150 | + |
| 151 | +JavaScript implementation: |
| 152 | + |
| 153 | +```js |
| 154 | +/* |
| 155 | + * @param memo - cache storage object |
| 156 | + */ |
| 157 | +const memo = {}; |
| 158 | + |
| 159 | +/** |
| 160 | + * Time complexity: O(n ^ 2) |
| 161 | + * Space complexity: O(n) |
| 162 | + * |
| 163 | + * @param number N |
| 164 | + * @return number |
| 165 | + */ |
| 166 | +function getFibonacciNumberRecursive(N) { |
| 167 | + if (N <= 1) { |
| 168 | + return N; |
| 169 | + } |
| 170 | + if (!memo[N]) { |
| 171 | + memo[N] = getFibonacciNumberRecursive(N - 1) + getFibonacciNumberRecursive(N - 2); |
| 172 | + } |
| 173 | + return memo[N]; |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +In recursion tree of calls example, (5) or F(5) function make |
| 178 | +one execution with same arguments: |
| 179 | +- F(2) - 1 times |
| 180 | +- F(3) - 1 times |
| 181 | + |
| 182 | +For optimization memoization method time complexity, we can storing the previous two numbers only |
| 183 | +because that is all we need to get the next Fibonacci number in series. |
| 184 | + |
| 185 | +JavaScript iterative implementation |
| 186 | + |
| 187 | +```js |
| 188 | + |
| 189 | +/** |
| 190 | + * Iterative |
| 191 | + * Time complexity: O(n) |
| 192 | + * Space complexity: O(1) |
| 193 | + * |
| 194 | + * @param number N |
| 195 | + * @return number |
| 196 | + */ |
| 197 | +function getFibonacciNumberIterative(N) { |
| 198 | + if (N <= 1) { |
| 199 | + return N; |
| 200 | + } |
| 201 | + let a = 0; |
| 202 | + let b = 1; |
| 203 | + let sum = null; |
| 204 | + |
| 205 | + for (let i =2; i <= N; i++) { |
| 206 | + sum = a + b; |
| 207 | + a = b; |
| 208 | + b = sum; |
| 209 | + } |
| 210 | + |
| 211 | + return b; |
| 212 | +} |
| 213 | +``` |
| 214 | + |
| 215 | +Alternative optimization recursion version, can be implementation of [tail recursion](https://en.wikipedia.org/wiki/Tail_call). |
| 216 | +Calculate the results first, and pass the results of your current call onto the next recursive call. |
| 217 | + |
| 218 | +Tail recursion JavaScript implementation. |
| 219 | + |
| 220 | +```js |
| 221 | + |
| 222 | +/** |
| 223 | + * Time complexity: O(n) |
| 224 | + * Space complexity: O(n) |
| 225 | + * |
| 226 | + * @param number N |
| 227 | + * @return number |
| 228 | + */ |
| 229 | +function getFibonacciNumberTailRecursion (n, a = 0, b = 1){ |
| 230 | + if (n > 0) { |
| 231 | + return fib(n - 1, b, a + b) |
| 232 | + } |
| 233 | + return a |
| 234 | +} |
| 235 | + |
| 236 | +``` |
| 237 | + |
| 238 | +In the tail-recursive case, with each evaluation of the recursive call, the running total is updated. |
| 239 | + |
| 240 | +#### Bonus reference. |
| 241 | + |
| 242 | +Faster Math solution, not related to Dynamic programming. |
| 243 | + |
| 244 | +```js |
| 245 | +/** |
| 246 | + * Time complexity: O(1) |
| 247 | + * Space complexity: O(1) |
| 248 | + * |
| 249 | + * http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html |
| 250 | + * @param number N |
| 251 | + * @return number |
| 252 | + */ |
| 253 | +function getFibonacciNumberMath (N){ |
| 254 | + return parsetInt(Math.round(Math.pow((1 + Math.sqrt(5)) / 2, N) / Math.sqrt(5))); |
| 255 | +} |
| 256 | +``` |
| 257 | + |
| 258 | +Formula [reference](http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html). |
| 259 | + |
0 commit comments