Algorithms #13

Dynamic programming

Those who cannot remember the past are condemned to repeat it.

George Santayana

Dynamic programming

Dynamic Programming

What is it actually ?

The word dynamic was chosen by Bellman to capture the time-varying aspect of the problems, and because it sounded impressive.

Richard Ernest Bellman

American applied mathematician, who introduced dynamic programming in 1953, and made important contributions in other fields of mathematics.

The core idea of Dynamic Programming is to avoid repeated work by remembering partial results and this concept finds it application in a lot of real life situations

1+1+1+1+1+1+1+1 =

What's that equal to?

8

1+

9

How'd you know it was nine so fast?

You just added one more

So you didn't need to recount because you remembered there were eight! Dynamic Programming is just a fancy way to say 'remembering stuff to save time later

In programming, Dynamic Programming is a powerful technique that allows one to solve different types of problems in time O(n^2) or O(n^3) for which a naive approach would take exponential time.

Dynamic Programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions using a memory-based data structure (array, map,etc).

Overlapping sub-problems

The two required properties of dynamic programming are

  • Optimal substructure: optimal solution of the sub-problem can be used to solve the overall problem.
  • Overlapping sub-problems: sub-problems recur many times. Solutions of sub-problems can be cached and reused

Let's try to understand this by taking an example of Fibonacci numbers.

/** 
    Fibonacci (n) = 1; if n = 0
    Fibonacci (n) = 1; if n = 1
    Fibonacci (n) = Fibonacci(n-1) + Fibonacci(n-2)
*/
function fib(n) {

   // implement this

}

Try to find 30th, 31th fib number

What is the complexity ?

How we can improve things ?

/** 
    Fibonacci (n) = 1; if n = 0
    Fibonacci (n) = 1; if n = 1
    Fibonacci (n) = Fibonacci(n-1) + Fibonacci(n-2)
*/
function fib(n, cache) {

   // implement this

}

Rewrite a function to use cache

You can't solve all algorithmic problems with a dynamic programming

What kind of problems can be solved using dynamic programming ?

Optimization problems

Combinatorial problems

 finding the best solution from all feasible solutions

Where can be many solutions. And we wish to find the optimal value

finding an optimal object from a finite set of objects

figure out the number of ways to do something, or the probability of some event happening

Real life applications of dynamic programming?

  • In Google Maps to find the shortest path
  • in the field of Biology to find the matching trends between two genes
  • git merge
  • In large warehouses, box packing with minimal cost
  • LaTeX 
  • artificial intelligence, automatic speech recognition
  • Time-sharing: It schedules the job to maximize CPU usage
  •  Optimal Control, for starters, which is applicable in aerospace systems, chemical control systems, robotics, and much more
  • Other optimization problems

Lets start with simple problems

Stairs problem

You can climb 1 or 2 stairs with one sep. How many different ways can you climb n stairs ?

function stairs(n, /* cache ? */) {
     // implement this
}

General steps of solving DP

1. Solve base cases

2. Define what is n-th value means 

3. Get the recurent formula

4. Define base cases and constraints

5. Define the order of calculation

6. Find out the result

Rods problem

You are given a rod of size n >0, it can be cut into any number of pieces k (k ≤ n). Price for each piece of size i is represented as p(i) and maximum revenue from a rod of size i is r(i) (could be split into multiple pieces). Find r(n) for the rod of size n.

const prices = [1,5,8,9,10, 17, 17, 20, 24, 30]

function rodsRevenue(n, prices, /* cache ? */) {
     // implement this
}

console.log(rodsRevenue(5, prices));

Rod cutting problem is very much related to any real-world maximization problem we face.

Longest common substring

 function lcs(a = '', b = '') {
    
 }

 console.log(lcs('asdABCDas', '12345567_ABCD_89')) // ABCD

The longest common subsequence problem is a classic computer science problem, the basis of data comparison programs such as the diff utility, and has applications in bioinformatics. It is also widely used by revision control systems such as Git for reconciling multiple changes made to a revision-controlled collection of files.

Eggs Dropping Puzzle

Suppose that we wish to know which stories in a 100-story building are safe to drop eggs from, and which will cause the eggs to break on landing.

 

We make a few assumptions:

…..An egg that survives a fall can be used again.
…..A broken egg must be discarded.
…..The effect of a fall is the same for all eggs.
…..If an egg breaks when dropped, then it would break if dropped from a higher floor.
…..If an egg survives a fall then it would survive a shorter fall.
…..It is not ruled out that the first-floor windows break eggs, nor is it ruled out that the 100th-floor do not cause an egg to break.

Optimal Substructure

When we drop an egg from a floor x, there can be two cases

 

(1) The egg breaks

(2) The egg doesn’t break.

 

1) If the egg breaks after dropping from xth floor, then we only need to check for floors lower than x with remaining eggs; so the problem reduces to x-1 floors and n-1 eggs
 

2) If the egg doesn’t break after dropping from the xth floor, then we only need to check for floors higher than x; so the problem reduces to k-x floors and n eggs.

eggDrop(n, k) ==> Minimum number of trials needed to find the critical floor in worst case.

  k ==> Number of floors
  n ==> Number of Eggs

eggDrop(n, k) = 1 + min{max(eggDrop(n - 1, x - 1), eggDrop(n, k - x)): x in {1, 2, ..., k}}

Analyzing the problem

function eggDrop(eggs = 2, floors = 10) {
    // implement this
}

Implement a recursive solution!

function opt(eggs = 2, floors = 10) {
    if (floors === 0 || floors === 1 || eggs === 1) return floors;
    let min = Infinity;
    
    for (let f = 1; f <= floors; f++) {
        const cand = Math.max(opt(eggs - 1, f - 1), opt(eggs, floors - f));
        if (cand < min) {
            min = cand;
        }
    }

    return min + 1;
}

Modify a solution to use dynamic programming concept!

function eggDrop(eggs, floors) { 
    /* A 2D table where entery eggFloor[i][j] will represent minimum 
    number of trials needed for i eggs and j floors. */
     const eggFloor = Array.from({ length: eggs + 1}, () => []); 
     let res = Infinity; 

        
     // We need one trial for one floor and0 trials for 0 floors 
     for (let i = 1; i <= eggs; i++) { 
         eggFloor[i][1] = 1; 
         eggFloor[i][0] = 0; 
     } 
        
    // We always need j trials for one egg and j floors. 
     for (j = 1; j <= floors; j++) {
         eggFloor[1][j] = j; 
     }
         
        
     // Fill rest of the entries in table using optimal substructure property 
     for (i = 2; i <= eggs; i++) { 
         for (j = 2; j <= floors; j++) {
             eggFloor[i][j] = Infinity; 
           
             for (x = 1; x <= j; x++) { 
                  res = 1 + Math.max(eggFloor[i-1][x-1], eggFloor[i][j-x]); 
                  if (res < eggFloor[i][j]) 
                     eggFloor[i][j] = res; 
             } 
         } 
     } 
        
     // eggFloor[eggs][floors] holds the result 
     return eggFloor[eggs][floors]; 
 } 

Another way to use cache (optimal substructure, bottom -> up upproach)

Thank you!

Algorithms #13

By Vladimir Vyshko

Algorithms #13

Dynamic Programming intro

  • 427