I am having trouble understanding the reasoning behind the solution to this question on CareerCup.
Pots of gold game: Two players A & B. There are pot
a
and b
here represent the maximum A
can get by picking the starting pot or the ending pot, respectively.
We're actually trying to maximize A-B
, but since B = TotalGold - A
, we're trying to maximize 2A - TotalGold
, and since TotalGold
is constant, we're trying to maximize 2A
, which is the same as A
, so we completely ignore the values of B
's picks and just work with A
.
The updated parameters in the recursive calls include B
picking as well - so coin[start]
represents A
picking the start, then B
picks the next one from the start, so it's start+2
. For the next call, B
picks from the end, so it's start+1
and end-1
. Similarly for the rest.
We're taking the min
, because B
will try to maximize it's own profit, so it will pick the choice that minimizes A
's profit.
But actually I'd say this solution is lacking a bit in the sense that it just returns a single value, not 'an optimal strategy', which, in my mind, would be a sequence of moves. And it also doesn't take into account the possibility that A
can't win, in which case one might want to output a message saying that it's not possible, but this would really be something to clarify with the interviewer.
First of all a
and b
represent respectively the maximum gain if start
(respectively end
) is played.
So let explain this line:
int a = coin[start] + min(max_coin(coin, start+2, end), max_coin(coin, start+1, end-1))
If I play start
, I will immediately gain coin[start]
. The other player now has to play between start+1
and end
. He plays to maximize his gain. However since the number of coin is fixed, this amounts to minimize mine. Note that
start+1
I'll gain max_coin(coin, start+2, end)
end
Ill gain max_coin(coin, start+1, end-1)
Since he tries to minimize my gain, I'll gain the minimum of those two.
Same reasoning apply to the other line where I play end
.
Note: This is a bad recursive implementation. First of all max_coin(coin, start+1, end-1)
is computed twice. Even if you fix that, you'll end up computing lots of time shorter case. This is very similar to what happens if you try to compute Fibonacci numbers using recursion. It would be better to use memoization or dynamic programming.
Assume what you gain on your turn is x
and what you get in all consequent turns is y
. Both values represent x+y
, where a
assumes you take next pot (x=coin[start]
) from the front and b
assumes you take your next pot (x=coin[end]
) from the back.
Now how you compute y
.
After your choice, the opponent will use the same optimum strategy (thus recursive calls) to maximise his profit, and you will be left with a the smaller profit for the turn. This is why your y=min(best_strategy_front(), best_strategy_end())
-- your value is the smaller of the two choices that are left because the opponent will take the bigger.
The indexing simply indicates the remaining sequences minus one pot on the front and on the back after you made your choice.
A penny from my end too. I have explained steps in detail.
public class Problem08 {
static int dp[][];
public static int optimalGameStrategy(int arr[], int i, int j) {
//If one single element then choose that.
if(i == j) return arr[i];
//If only two elements then choose the max.
if (i + 1 == j ) return Math.max(arr[i], arr[j]);
//If the result is already computed, then return that.
if(dp[i][j] != -1) return dp[i][j];
/**
* If I choose i, then the array length will shrink to i+1 to j.
* The next move is of the opponent. And whatever he choose, I would want the result to be
* minimum. If he choose j, then array will shrink to i+1, j-1. But if also choose i then
* array will shrink to i+2,j. Whatever he choose, I want the result to be min, hence I take
* the minimum of his two choices.
*
* Similarly for a case, when I choose j.
*
* I will eventually take the maximum of both of my case. :)
*/
int iChooseI = arr[i] + Math.min(optimalGameStrategy(arr, i+1, j-1),
optimalGameStrategy(arr, i+2, j));
int iChooseJ = arr[j] + Math.min(optimalGameStrategy(arr, i+1, j-1),
optimalGameStrategy(arr, i, j-2));
int res = Math.max(iChooseI, iChooseJ );
dp[i][j] = res;
return res;
}
public static void main(String[] args) {
int[] arr = new int[]{5,3,7,10};
dp = new int[arr.length][arr.length];
for(int i=0; i < arr.length; i++) {
for(int j=0; j < arr.length; j++) {
dp[i][j] = -1;
}
}
System.out.println( " Nas: " + optimalGameStrategy(arr, 0, arr.length-1));
}
}
Let me answer your points in reverse order, somehow it seems to make more sense that way.
3 - a and b represent the amount of coins the first player will get, when he/she chooses the first or the last pot respectively
2 - we take the minimum because it is the choice of the second player - he/she will act to minimise the amount of coins the first player will get
1 - the first line presents the scenario - if the first player has taken the first pot, what will the second player do? If he/she again takes the first pot, it will leave (start+2, end). If he/she takes the last pot, it will leave (start+1, end-1)