Sunday, July 30th

How many turns for a strange printer?

Consider a strange printer, which can only print a series of the same character at a time.  Given a start and an end, it prints each character, overriding any previous character that may have been there.  Given a string, determine the minimum number of turns for the strange printer to produce the string.


The problem asks for the minimum of something.  Also, we can see that with characters overlapping one another that there are overlapping subproblems.   This makes the case that dynamic programming may be an appropriate approach.

The Editorial provides a detailed look at the problem from bottom up.  A helpful video from user vanAmsen also goes in depth bottom up.  However, this problem is a bit complicated, and the rule-of-thumb for dynamic programming problems is that the top down approach is easier to conceptualize and implement (while bottom-up has a faster runtime).

To establish our recursive function, let's consider the state variables.  We have a string, and we want to determine how many turns are required.  We will break this into subproblems, which means parts of the string.  We can represent this substring by the left and right indices on the original string.  Thus, our state variables are left and right.

We initialize a 2D memo that, for given indices left and right (or and j) will return the number of turns to create the given substring.  The 2 indices can be from 0 to n, the length of the string. The values are initialized to -1.  

vector<vector<int>> memo;

memo = vector<vector<int> >(n, vector<int>(n,-1));

We define a recursive function that accepts the 2 indices as input and delivers the number of turns to create the defined substring

int dp(int i, int j){
            ....
        }

Within the dp function, we first check if the substring has already been computed

if (dp[i][j]!=-1) return dp[i][j];

Then we test for the base case, which is when i==j.  Then it obviously returns 1, as only 1 operation is required to print the correct character

if (i==j) return dp[i][j]=1;

Next, we initialize the result variable, and check for a couple of border cases.  If the first character of a given substring is equal to the last character, or if the penultimate character equals the final character, then we have an answer equal to the answer of the substring i and j-1

 int res;
    if (s[i]==s[j]||s[j-1]==s[j]) res=f(i, j-1);

Similarly, if the first character and second character are equal, the problem becomes the same as a substring from I+1 to j

else if (s[i]==s[i+1]) res=f(i+1, j);

Those 2 lines above are the border cases.  If neither is true, then to find the number of turns to make the substring, we start with the maximum number of turns possible, which is the number of turns to make i to j-1 + 1.  Can this be improved?  We iterate over all intermediate elements from the start to end.  If any of these intermediate values equal the s[j], the end of the string, then there may be a string of letters larger than 1, which would result in a number of turns less than the res at the beginning.  To determine the number of turns, we call the dp function recursively.

    else{
res=f(i,j-1)+1;
for(int k=i+1; k<j; k++){
if(s[k]==s[j])
res=min(ans, f(i, k-1)+f(k, j-1));
}
}
    }

Finally we update the dp value

return dp[i][j]=res;

In the main function, we call the dp function on indices 0 and n-1, since solving for the last element isn't necessary

return dp(0, n-1);

This was a challenging problem with multiple approaches.  Thank you to user anwendeng for a clear top down implementation

Comments

Popular posts from this blog

Wednesday, September 20th

Monday, September 11th

Tuesday, September 19th