Dynamic Programming and the partition problem
By: Sweta Barman
Tags: Leetcode, Algorithms, Programming, Dynamic Programming

The first thing to note about Dynamic Programming is that — you cannot memorize the patterns! Memoize, but do not memorize!

If you understand recursion — dynamic programming is just one and a half steps ahead of recursion. Always, Always, and I cannot emphasize it enough — ALWAYS come up with a recursive solution first!

The steps to approach a DP problem are :

Write out the recurrence relation

Write out code for the recursive solution (based on the recurrence relation)

Try to cache(using a HashMap or any other data structure you prefer) the repetitive function calls —this is known as top-down dynamic programming

Rack your brain to come up with a bottom-up DP approach. (Preferrably using a 2D-array)

Try to push yourself a little further to use a 1D array instead of a 2D array for the bottom-up solution

Ignore the previous step- if you’re losing interest :D

So, I picked up this problem from Jeff Erickson’s book on Algorithms : (http://algorithms.wtf) [Please check out his book. It is hands-down the best book on algorithms!]

Given an array A[1 .. n] of characters, compute the number of partitions of A into words. For example, given the string ARTISTOIL, your algorithm should return 2, for the partitions ARTIST·OIL and ART·IS·TOIL.

Let us assume that we are provided with a dictionary of the required word. If we call on the subroutine isWord(string), it returns true if string is a word in the dictionary and it returns false otherwise.

Step 1 : Write out the recurrence relation

So, our recurrence relation could be written as :

count = count + 1,

everytime: recurse(s[0…..i]) = true

recurse(s[0…n-1]) = isWord(s[0…k]) && recurse(s[k+1….n-1]) -> it runs from k = 0 to k = n-1

Step 2 : Write out code for the recursive solution

public class CountPartitions {



int count = 0;



public int countPartitions(String s, Set<String> words) {

recurse(s, words, 0, s.length());

return count;

}



/**

*

* @param s - input string

* @param set - dictionary of words

* @param start - start of the string

* @param end - end of the string

* @return

*/

private boolean recurse(String s, Set<String> set, int start, int end) {

if (start > s.length())

return false;

for (int i = start + 1; i < end; i++) {

String str = s.substring(start, i);

if (set.contains(str) && recurse(s, set, i, end))

count++;

}

return set.contains(s.substring(start, end));

}



}

Time complexity : Exponential.

Step 3 : Try to cache repetitive function calls — this is known as top-down dynamic programming

What if we introduced a hashmap and we are able to cache the intermediate results as we recurse. If we try to hit a function call that we have encountered before, we just return the cached result.

/**

*

* @param s - input string

* @param set - dictionary of words

* @param start - start index

* @param end - end index

* @param cache - cache for storing intermediate results - useful for DP top-down approach

* @return

*/

private boolean recurse(String s, Set<String> set, int start, int end, Map<String, Boolean> cache) {

if (start > s.length())

return false;

String substring = s.substring(start, end);

if (cache.containsKey(substring))

return cache.get(substring);

for (int i = start + 1; i < end; i++) {

String str = s.substring(start, i);

if (set.contains(str) && recurse(s, set, i, end, cache)) {

count++;

String substr = s.substring(start, end);

cache.put(substr, true);

}

}

return set.contains(s.substring(start, end));

}

Step 4 : Rack your brain to come up with a bottom-up DP approach.

Okay. So far, so good. We have the top-down solution. Now, how do we come up with a bottom-up 2D approach?

recurse(s[0…n-1]) could be translated to : dp[0][n-1], where 0 represents the start and n-1 represents the end of the string.

Hence, dp[0][n-1] = true, if (dp[i][j] == true && dp[j+1][n-1] == true)

count = count + 1, if( dp[0][j] == true && isWord(s.substring(j+1, n))

public int dynamicProgrammingSolution(String s, Set<String> set) {

boolean[][] dp = new boolean[s.length()][s.length()];

for (int i = 0; i < s.length(); i++) {

for (int j = i; j < s.length(); j++) {

String str = s.substring(i, j+1);

if (set.contains(str))

dp[i][j] = true;

}

}

int count = 0;

for (int i = 0; i < s.length(); i++) {

for (int j = i + 1; j < s.length(); j++) {

for (int k = j+1; k < s.length(); k++) {

if (dp[i][j] && dp[j + 1][k])

dp[i][k] = true;



}

}

}

int i = 0;

for (int j = i; j < s.length()-1; j++) {

if (dp[i][j] && set.contains(s.substring(j + 1)))

count++;

}

return count;

}

Time complexity = O(n³).

This looks cumbersome! Surely, there would be a better way of implementing this?

Step 5 : Try to push yourself a little further to use a 1D array instead of a 2D array for the bottom-up solution

Could we use a 1D array instead? Looks like we could!

We only care about dp[0][n-1]. What if, dp[0][n-1] translates to dp[n-1] ?

Then, count = count + 1, if (dp[i] && isWord(s.substring(i+1, n))).

The code for the 1D solution could be written as :

public int dynamicProgrammingSolution1D(String s, Set<String> set) {

boolean[] dp = new boolean[s.length()+1];

dp[0] = true;

for (int i = 1; i < s.length() + 1; i++) {

for (int j = 0; j < i; j++) {

if (dp[j] && set.contains(s.substring(j, i)))

dp[i] = true;

}

}

int count = 0;

for (int i = 0; i < s.length(); i++) {

if (dp[i] && set.contains(s.substring(i)))

count++;

}

return count;

}

Time complexity : O(n²). Space complexity : O(n)!

Did you try out a DP problem by following the approach mentioned above? If, yes — please write out in the comments section!