Hackerrank DP: Sherlock and Cost
By: Rohan Gupta
Tags: Dynamic Programming, Coding, Interview, Programming, Interview Questions

Hello there!

In this article, we’ll look at this hackerrank question Sherlock and Cost.

Problem Statement

In this challenge, you will be given an array B and must determine an array A. There is a special rule: For all i, A[i]≤B[i]. That is A[i], can be any number you choose such that 1≤A[i]≤B[i]. Your task is to select a series of A[i] given B[i] such that the sum of the absolute difference of consecutive pairs of A is maximized. This will be the array’s cost, and will be represented by the variable S below.

The equation can be written:

For example, if the array B = [1, 2, 3], we know that 1≤A[1]≤1, 1≤A[2]≤2, and 1≤A[3]≤3. Arrays meeting those guidelines are:

[1,1,1], [1,1,2], [1,1,3]

[1,2,1], [1,2,2], [1,2,3]

Our calculations for the arrays are as follows:

|1-1| + |1-1| = 0 |1-1| + |2-1| = 1 |1-1| + |3-1| = 2

|2-1| + |1-2| = 2 |2-1| + |2-2| = 1 |2-1| + |3-2| = 2

The maximum value obtained is 2.

Constraints

1≤t≤20

1<n≤10⁵

1≤B[i]≤100

Approaching the Problem

You need to maximize the consecutive difference here. What can be the possible directions in which we can think here? According to me, it can be greedy, DP, or graph. Let’s approach greedy first.

We can clearly make one intuitive observation here — for maximizing the difference you can either choose an element to be 1 or B[i].

Greedy Approach

The above observation may prompt us to think that we alternate the 1's and B[i]’s. Now, let me tell you that won’t be optimal. Why?

Try this example out-

55 89 73 45 29 12 83 57 22 70

The answer comes out to be 467 by the alternating 1s and B[i] approach. How?

1 89 1 45 1 12 1 57 1 70

88 + 88 + 44 + 44 +11 +11 +56 + 56 +69 = 467

However it’s real answer is 497. How? Look at this.

1 89 1 45 1 12 83 1 22 70

So, the greedy approach is out of the picture.

Recursive Approach

As we saw above, we have only two options. Either choose 1 or B[i]. We can formulate this idea into a recursion as follows:

Function Signature:

int rec(vector<int>& B, int i, bool flag);

Here, the array B is the input array, i denotes the current index of the array, flag denotes if the previous element was chosen to be B[i] or 1. If flag==1, then previous element was chosen to be 1, else, vice-versa.

Base case:

If the size of array is less than 0, which is impossible, then the answer is 0.

if(i==-1)

return 0;

Recursive Calls:

There are two cases, current element is chosen to be 1, then set the flag to 0 and recur for i-1 else set the flag to 1 and recur for i-1.

int a=0, b=0; a = rec(B,i-1,0); b = rec(B,i-1,1);

Return value:

Adding the absolute difference of the current chosen element to the previously chosen element and adding to the result.

int val = (flag) ? B[i+1] : 1; a = a + abs(val-1); b = b + abs(val - B[i]); return max(a,b);

Final recursive code:

int rec(vector<int>& B, int i, bool flag){ if(i==-1){ return 0; } int a=0, b=0; a = rec(B,i-1,0); b = rec(B,i-1,1); int val = (flag) ? B[i+1] : 1; a = a + abs(val-1); b = b + abs(val - B[i]); return max(a,b); } int cost(vector<int> B) { vector<vector<int>> dp(B.size(), vector<int>(2, -1)); return max(rec(B,B.size()-2, 0 ), rec(B,B.size()-2, 1)); }

However, this will timeout.

Dynamic Programming Approach

There are overlapping subproblems in the above recursive approach. Hence, we can try out dynamic programming.

The intuition is to memoize the recursive approach and obtain a result. However, we are storing the two recursive calls to two seperate variables which means we’ll have to handle the memoization for both the recursive calls. This makes our code prone to errors and prone to TLE as well if not handled carefully.

So, let’s try to solve it using tabulation. We can take the help of our recursive solution to write an iterative solution.

DP Table:

int dp[B.size()][2];

dp[i][0] will mean, maximum possible absolute difference till ith index if the ith element is chosen to be B[i].

dp[i][1] will mean, maximum possible absolute difference till ith index if the ith element is chosen to be 1.

Initialization:

dp[0][0] = dp[0][1] = 0;

if i==0, the result will anyhow be 0 because if our array has only 1 element then the absolute difference will be 0.

DP Logic:

dp[i][0] = max(dp[i-1][0]+abs(B[i]-B[i-1]), dp[i-1][1]+abs(B[i]-1));

dp[i][1] = max(dp[i-1][0]+abs(1-B[i-1]), dp[i-1][1]);

dp[i][0] will be max of :

// dp[i][0] means current element is chosen to be B[i]. 1. dp[i-1][0]+abs(B[i]-B[i-1]) // maximum absolute difference till i-1th index such that element at i-1th index is chosen to be B[i-1]. This is why we add absolute difference between B[i] and B[i-1]. 2. dp[i-1][1]+abs(B[i]-1) // maximum absolute difference till i-1th index such that element at i-1th index is chosen to be 1. This is why we add absolute difference between B[i] and 1.

dp[i][1] will be max of:

// dp[i][1] means current element is chosen to be 1. 1. dp[i-1][0]+abs(1-B[i-1]) // maximum absolute difference till i-1th index such that element at i-1th index is chosen to be B[i-1]. This is why we add absolute difference between 1 and B[i-1]. 2. dp[i-1][1] // maximum absolute difference till i-1th index such that element at i-1th index is chosen to be 1. Since, current chosen element is 1 and element at i-1th index was also 1, the absolute difference comes out to be 0.

Final Code:

int cost(vector<int> B) {

int dp[B.size()][2];

dp[0][0] = dp[0][1] = 0;

for(int i=1;i<B.size();i++){

dp[i][0] = max(dp[i-1][0]+abs(B[i]-B[i-1]), dp[i-1][1]+abs(B[i]-1));

dp[i][1] = max(dp[i-1][0]+abs(1-B[i-1]), dp[i-1][1]);

}

return max(dp[B.size()-1][0], dp[B.size()-1][1]);

}

That’s all. The time complexity of this code is O(2*n) or O(n) which is enough to pass the constraints of 10⁵.

Hope this helped!