Sunday, August 27th

 A river-crossing frog

Given an arrangement of stones, and the ability of a frog to jump k-1, k, or k+1 distances, determine if the frog can successfully cross the river

A variegated golden frog, taken by Charles J. Sharp, distributed by via Wikipedia under the Creative Commons Attribution-Share Alike 4.0 International license.  No changes made

The dependencies of previous answers to later answers demonstrates overlapping subproblems, and is a candidate for solving with dynamic programming.

We first identify our state variables.  What does each subproblem consist of? There is the frog's index on the stones array, we are trying to find if the frog can travel from one index to the last index.  Also, since the distance a frog can jump depends on the previous jump, the second state variable is the length of the previous jump.

With the state variables in mind, we can begin developing the dynamic programming recursive function dp.  It accepts 2 variables, the index i, and the previous step k.  The function will return true a route can be found, false if otherwise.

bool dp(int i, int k) {
    ...
}

The first condition to test for is if we have reached the end of the river.  If so, return true;

    if (i == stones.size() - 1) {
return true;
}

Next, since this is dynamic programming, we need to develop a memo for storing intermediate results.  The memo will be 2 dimensions, for each state. variable.  The problem constraints limits the size of the river to 2000 stones, so our position index and step length will be capped by 2001.  

Additionally, we initialize a map that maps stone positions to indices.  This will be used when testing if a given frog jump lands on a stone.  For convenience, to restrict the number of arguments to the dp function, we make the stones input array a global variable


vector<int> stones;
unordered_map<int, int> sm; //stone map
vector<vector<int> > memo;

 ...

    bool canCross(vector<int>& stones) {
if(stones[1]!=1) return false;
this->stones=stones;

for (int i = 0; i < stones.size(); i++) {
sm[stones[i]] = i;
}
memo=vector<vector<int> >(2001, vector<int>(2001,-1));

        ...
}

Back in the dp function, we test if a given state has already been calculated

bool dp(int i, int k) {
...
if (memo[i][k] != -1) {
return memo[i][k];
}
        ...
    }

Now we come to the recurrence relation.  There are three possible state transitions, one for each type of step, whether, with k begin the length of the previous jump, k-1, k, or k+1 jump lengths.  For each step length, we create a boolean variable

bool k0 = false;
bool kp = false;
bool k1 = false;

Then for each variable, we determine if a jump of that length is possible.  Using the map, we test if a jump will land on a stone, and if it does, recursively call the dp() function with updated state variables for index and jump size.

First we use the stone map, sm, to test if such a stone exists, eg

    sm.find(stones[i] + k) != sm.end()

And if so, we call the function recursively, with the index updated and step updated, e.g.

    k0 = dp(sm[stones[i] + k], k);

For a step of the same size:

if (sm.find(stones[i] + k) != sm.end()) {
k0 = dp(sm[stones[i] + k], k);
}

For a step of one less, we also make sure it's not a non-jump

if (k > 1 && sm.find(stones[i] + k - 1) != sm.end()) {
kp = dp(sm[stones[i] + k - 1], k - 1);
}

And for jump lengths of k+1

if (sm.find(stones[i] + k + 1) != sm.end()) {
k1 = dp(sm[stones[i] + k + 1], k + 1);
}

To complete the dp function, we update the memo using a logical OR of the three recursive calls and return the result

memo[i][k] = k0 || kp || k1;
return memo[i][k];

Finally, we add the initial recursive call to the main function

return dp(1, 1);

A two state dp problem, with the added complexity of the reverse lookup map

Thanks to user shivam-727 for organizing the dp function into 3 boolean variables

Comments

Popular posts from this blog

Wednesday, September 20th

Monday, September 11th

Tuesday, September 19th