Hi there! Today we will try to generalize an approach in which we can tackle dynamic programming problems. For queries such as what is dynamic programming? When to use dynamic programming? please refer to standard text. We will first list down an approach and then try applying it to a bunch of problems. Let’s begin!

We can view dynamic programming problems as an attempt to figure out state changes from one state to another. In order to solve such problems all we have to do is to figure out a way to represent our problem instance as a state and a transition function(a mechanism to move from one state to another). Figuring out how to represent our problem with a state is generally hard in such scenarios as transition function can be easily derived from the constraints mentioned in the problem statement.

How do I get started on State definition? The way I approach state definition is by trying to define a state for the final goal. We know from problem definition what we are looking for… and hence it is easy to define a state for that situation. Once you have modeled your final solution into a state, trace back to figure out other states and transition function to enable yourself to move from one state to another.

Hence our general strategy looks as follows:

Step 1: Define a state for the final solution

Step 2: Figure out other states and a transition function to move in between states.

Step 3: Build up solution in a bottom up fashion on paper

Step 4: Write code, Validate and be happy

The key to mastering dynamic programming (for that matter anything) is practice. Hence we will now apply the above-mentioned strategy to various problems and build confidence in tackling such scenarios. By the end of this problem-solving exercise, you yourself would be able to contribute a thing or two to the strategy for solving dynamic problem. Don’t shy away from commenting at that stage as it enables others to learn about new perspectives on a given topic.

Problem 1: Longest Increasing Subsequence

Problem Statement:

The longest Increasing Subsequence (LIS) problem is to find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order. For example, length of LIS for { 10, 22, 9, 33, 21, 50, 41, 60, 80 } is 6 and LIS is {10, 22, 33, 50, 60, 80}.

Approach:

Let us start by building up a state for the final solution. Whenever dealing with arrays, some of the options are to choose either suffixes, prefixes or a range. Let us define LIS(n) to be the length of the longest increasing subsequence. Is this enough? By this definition it will be difficult to derive this result from other sub-problems such as LIS(n-1), LIS(n-2) etc. Hence we must re-define our state as LIS(n) = length of longest increasing subsequence including arr[n]. This way we can build LIS(n) using other sub-problem states. We will consider all LIS from 0 to n-1 and if arr[n] is greater than that element, we have one increasing subsequence to consider ending with arr[n].

Hence our transition function is as follows:

L(i) = { 1 + Max ( L(j) ) } where j < i and arr[j] < arr[i] and if there is no such j then L(i) = 1

Let’s work out our solution on a small problem sample and then we’ll code it.

arr = { 10, 22, 9, 33 };

LIS(0) = 1 since there is nothing to the left of 10

LIS(1) = max(LIS(0)+1/*since 10 is less than 22*/, 1) = 2

LIS(2) = max(1 /*since nothing is smaller than 9 that is to the left of 9*/) = 1

LIS(3) = max(LIS(0) +1, LIS(1) + 1, LIS(2) + 1, 1) = 3

Now how do we get our answer ? Since we don’t care about what is the ending element and just want our length of the longest increasing subsequence for the entire array, just loop through all LIS and pick the maximum one. In above example, ans is 3 an sequence is 10,22,33

Code:

// Returns length of longest increasing subsequence ending at index i // given an input array and the index i and lis values computed for all // index less than i int LongestIncreasingSubsequenceEnding(int* arr, int i, int* LISEnding) { int ans = 1; for (int j = 0; j < i; j++) { if (arr[j] < arr[i]) ans = max(ans, (LISEnding[j] + 1)); } return ans; } // Returns length of the longest increasing subsequence // given an array int LongestIncreasingSubsequence(int* arr, int length) { int* LISEnding = new int[length]; for (int i = 0; i < length; i++) { LISEnding[i]=LongestIncreasingSubsequenceEnding(arr, i, LISEnding); } int ans = 0; for (int i = 0; i < length; i++) { if (LISEnding[i] > ans) ans = LISEnding[i]; } return ans; }

Problem 2: Longest Common Subsequence

Problem Statement:

Given two sequences, find the length of longest subsequence present in both of them. A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous. For example, “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”.

LCS for input Sequences “ABCDGH” and “AEDFHR” is “ADH” of length 3.

LCS for input Sequences “AGGTAB” and “GXTXAYB” is “GTAB” of length 4

Approach:

Let us first start by defining a state for the final solution. Let the length of first string be m and for second string be n. Then LCS[m][n] is the length of longest common subsequence considering string str1 from 0 to m-1 and string str2 from 0 to n-1(that is entire strings).

Can we relate this to earlier sub-problem states? Yes, if the final characters match the one of the common subsequence would be LCS[m-1][n-1] + 1 /*for matched character*/ or else we can ignore last character from each of the strings and figure out the max common sequence length.

Hence our transition function is as follows,

LCS[i][j] = max(LCS[i-1][j-1] + 1, if str1[i] == str2[j],

LCS[i-1][j], // ignore last character of first string

LCS[i][j-1]) // ignore last character of second string

LCS[i][j] would be zero if either I or j is less than 0 as we don’t have any string before index 0

Now lets work out our solution on a small sample problem and then code it

Str1 = “AB”

Str2 = “AC”

LCS[0][0] = max(1 + LCS[-1][-1]/*Str1[0] == Str2[0]*/, LCS[-1][0], LCS[0][-1]) = 1

LCS[0][1] = max(LCS[0][0], LCS[-1][1]) = 1 /* first term is not there as Str1[0] != Str2[1] */

LCS[1][0] = max(LCS[0][0], LCS[1][-1]) = 1;

LCS[1][1] = max(LCS[0][1], LCS[1][0]) = 1

Hence our ans is LCS[1][1] which is 1. It is clear from the above example that to solve this problem we need to build this two dimensional matrix of m x n size row-wise.

Code:

int LongestCommonSubsequence(string str1, string str2) { int length1 = str1.length(); int length2 = str2.length(); int dpArray[length1][length2]; for (int i = 0; i < length1; i++) { for (int j = 0; j < length2; j++) { int ans = 0; if (str1.at(i) == str2.at(j)) { ans++; if (i - 1 >= 0 && j - 1 >= 0) ans += dpArray[i - 1][j - 1]; } if (i - 1 >= 0) ans = max(ans, dpArray[i - 1][j]); if (j - 1 >= 0) ans = max(ans, dpArray[i][j - 1]); dpArray[i][j] = ans; } } return dpArray[length1 - 1][length2 - 1]; }

Problem 3: Best Time to buy and sell stocks I

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit

Approach:

Let us define the state for final solution. Here we cant just define profit[n] as the max profit till nth day, because in order to relate with previous computed profit we must know when was the stock bought and when sold. Hence let us define two states – sell[n] as the maximum profit obtained by selling on day n or before and buy[n] as the maximum profit obtained by selling on day n or before. Now we can relate them as –

Sell[n] = max(Sell[n-1], buy[n-1] + Arr[n])

Buy[n] = max(-Arr[n], Buy[n-1])

Also base cases are as follows:

Sell[0] = INT_MIN // You cant sell without buying

BUY[0] = -Arr[0]

As we can see we only need states I and i-1 at any point. Hence we don’t need to build any dp tables here and can solve this using two variables to store these states.

Now lets work out our approach on a sample problem:

Arr = {1, 2, 3}

Buy[0] = -1 Buy[1] = -1 Buy[2] = -1

Sell[0] = INT_MIN

Sell[1] = max(INT_MIN, -1+2) = 1

Sell[2] = max(1, -1+3) = 2

Hence our ans is Sell[2] = 2

Code :

int maxProfit(const vector<int> &A) { int size = A.size(); if(size <= 1) return 0; // Cant do anything with just one day int buy = -A[0]; int sell = INT_MIN; for(int i = 1; i < size; i++) { sell = max(sell, buy + A[i]); buy = max(buy, -A[i]); } return sell > 0 ? sell : 0; }