Approaches To Problem Solving #6
By: Ankesh Krishna Prasad
Tags: Knowledge Sharing, Dynamic Programming, Share Learnings, Problem Solving, Sport Programming

Photo by XPS on Unsplash

In our previous blog, we already started with the introduction to one of the problems that we are going to discuss. We will try to discuss further about the topics involved.

Let us revisit the problem statement,

Problem #1: Given a chocolate bar of length N, we want to find out the number of ways of dividing this chocolate bar into exactly K pieces, where each of the K pieces has to be of positive integral length.

For the sake of simplicity, let us assume that we always make vertical cuts and two ways of cutting the chocolate are considered to be different from each other if there is at least 1 different cut made in one of the two.

Let us try to visualise the problem in order to understand what we are actually supposed to count.

Consider the following image which depicts a chocolate bar of length 3 and we have to cut it down into exactly 2 pieces.

{AB, BC, CD} can be visualised as different segments in the chocolate bar AD of length 3.

One way of obtaining the desired result is shown below,

Chocolate bar cut vertically at point B.

After cutting the bar vertically at point B, we obtain exactly two pieces namely {AB’} and {B”D}. One more way of obtaining two pieces would be to cut the chocolate at point C(that would be symmetric to the first way) and we can again obtain two pieces. Observe that both the pieces that we obtain in both the above cases have a positive integer length and hence, these are valid ways to cut the chocolate bar. So, the answer for N = 3 and K = 2 is exactly 2 because there are no other ways of cutting the chocolate of length 3 into 2 pieces given all the mentioned constraints.

So as we are clear about what we need to find now, let us discuss the ways of approaching such problems. First thing to notice here, is that in order to achieve K pieces of chocolate bar, we need to make exactly K — 1 cuts on it. Second thing to notice, is that the total number of candidate points on a chocolate bar of length N, where these cuts can be made is exactly N — 1(since we are never going to make a cut on the terminals). The above mentioned example is simple, because there is only one cut that we make in order to achieve our result. If we were to increase the number of cuts, we can see that the previous cuts decide the possibilities of the future cuts as we cannot cut the chocolate bar at the same position twice(goes without saying).

This is the point where one should realise that the given problem is getting broken into subproblems and the results of the subproblems are clearly affecting the result of the final problem. Does this ring any bell? It surely does! Dynamic Programming could be our way to go.

The problems where we have to count the number of ways of doing something and we can see that the subproblems are contributing towards the final solution, “in most of the cases” can be solved using dynamic programming. Such problems fall in the category of “Counting Problems” section of dynamic programming.

Let us now move on to how we can approach this problem,

Approach #1: It is a good habit to look at the subproblems in such problems as a function of some variables. We call these variables “States”. These variables are called so because they describe the state of the given subproblem that we are trying to solve. Let us try to decide the States for this problem. One hint that we can pick from the problem is that we are interested in finding the value of some function ‘f’ which takes in the length and the pieces into consideration and returns the answer.

Keeping that in mind our function ‘f’ will look something like f(N, K), where N is the length of the chocolate bar covered so far and K is the number of pieces achieved from this bar, traversing from length 1 to N.

We have deduced that the cuts needed to achieve exactly K pieces are K — 1, so let us add it to our parameters. The function f will now look something like f(N, K, K — 1). It is always good to keep in handy only those variables that are independently contributing to our solution as parameters to function f that we are interested in. We know that cuts and pieces are interdependent and one can be deduced from the other. Hence, we can remove one of them. Let us say that we remove pieces so our function should now look like f(N, K — 1). Similarly, first parameter can be replaced by the number of candidate points where a cut can be made i.e., N — 1. Since, there are no other variables that affect our solution, these two are the required states. Yep! We have successfully defined our states and we can swiftly move on to finding the other aspects that define how our dynamic programming works.

Now, it should be quite clear that the solution to some subproblem can be found by computing f(i, j) where ‘i’ is the number of cut candidate points of the chocolate bar that we have covered so far and ‘j’ is the number of cuts that we have made while traversing from length 1 to i.

As we have clearly defined how can we interpret the subproblems mathematically, we are yet to find the value of this interpretation f. What values does this f take, is defined by what we call “Transitions”. Since in this problem we are traversing from 1 to N and finding the solution, let us think about exploring different ways that bring us to a subproblem f(i, j). As we are taking 1 step in each traversal, it is quite intuitive that the previous subproblem would have the first parameter as i — 1. What are the different values that the second parameter can take? Is it just j — 1? Think about it. To have a clearer image, keep telling oneself that j simply means the cuts made so far. It is not necessary that we make a cut at every index. We can choose whether we make a cut or we don’t. When we try to think about choosing whether we make a cut on some index or not, it would be clear that the second parameter can either be j — 1 or j itself. Had it been j — 1, it would mean that we are choosing to make a cut at the index i, else we would choose not to make a cut. Hence, adding the ways of making these choices gives us the solution to the current subproblem.

Thus, our transitions would look something like,

f(i, j) = f(i — 1, j) + f(i — 1, j — 1) , where f(i, j) is answer to the current subproblem.

This recursive relationship that we achieved above defines Transitions for our dynamic programming.

In case it still seems unclear, do give it a read a few times and try to pen down the formulae in your own words and it would all make sense.

Wait! aren’t we forgetting something? Yes!! the above relationship is recursive and hence needs to have some checkpoint where it stops. We call these checkpoints, “Base Cases”. The base cases are easier to find out comparatively, in this problem.

We can observe that i and j both are decreasing in nature. Thus, they would reach a point where they become 0 and negative, respectively. It can be observed that if i becomes 0 we need to check if the cuts remaining (j) is exactly equal to 0 or not. If yes, we return 1(success), else return 0(failure). Similarly, if at any point, our cuts remaining (j) becomes negative, we need to return 0.

Whenever solving some dynamic programming problem, we should keep in mind that we are storing our answers for every different state value that our transitions can take. We call this technique, “Memoization”. So, we can define a matrix in our problem with rows being of size upto the length of the chocolate bar and columns being of size upto the number of cuts required. By storing these values, we can make sure that we don’t visit the already visited states through our transitions.

Time complexity for this solution is ~ O(N * K) and Space complexity for this solution is also ~O(N * K), where N is the length of the chocolate bar and K is the number of cuts needed to be made.

We can try to solve the following problem to have a better understanding of the above approach :-