Question: There are n coins in a line. Two players take turns to take a coin from one of the ends of the line until there are no more coins left. The player with the larger amount of money wins. Assume that you go first, describe an algorithm to compute the maximum amount of money you can win.
Answer: If we assume that n is even then there is simple strategy to ensure that you win. Find the sum of coins at even and odd positions say X and Y respectively. Now if X>Y, pick coins only at even positions else pick coins only at odd positions.
How to ensure that you pick coins only at even or odd positions:
Assume that n = 6 and value are {3, 2, 2, 3, 1, 2}. Then sum at even positions is 7 (2 +3 +2) and at odd positions is 6 (3 +2 +1). So you will always pick the coins at even positions to ensure wining. How to achieve it:
You first pick coin at position 6 (value 2). Now the opponent is forced to choose coin at either at position 1 or 5. If he picks coin at position 1, you choose coin at position 2. Else if he picks coin at position 5, you choose coin at position 4. Following this strategy you will always ensure picking even position coins (same for odd positions.)
Is above strategy optimal:
Above strategy ensure you winning but doesn’t ensure that you are drawing the maximum amount of money. For previous example {3, 2, 2, 3, 1, 2}, you drew a sum of 7, but you could have drawn a sum of 8. Lets see how:
Pick the first coin (3) instead. The opponent is left with two possible choices, the left coin (2) and the right coin (2), both valued at 2. No matter, which coin the opponent chose, you can always take the other coin (2) next and the configuration of the coins becomes: {2, 3, 1}. Now, the coin in the middle (3) would be yours to keep for sure. Therefore, you win the game by a total amount of 3 + 2 + 3 = 8, which proves that the previous non-losing strategy is not necessarily optimal.
An optimal Solution:
Since your opponent is as smart as you, so we need to take all the possible combinations in consideration before our move. Calculating all those moves will include repetitive calculations so this is the place where we can use Dynamic Programming to show its magic.
Say coins are in stored in array A and C(i, j) is the maximum sum possible when remaining coins are from i to j. Lets discuss the case when there are remaining coins from i to j (positions) and it’s your turn to pick the coin. Now there are 2 possibilities:
Case 1 - If you pick coin Ai : Then the opponent has the choice between A(i+1) and A(j). If the opponent takes A(i+1), the remaining coins are { A(i+2) … A(j)}, for which our maximum is denoted by C(i+2, j). On the other hand, if the opponent takes Aj our maximum is C(i+1, j-1). Since the opponent is as smart as you, he would have chosen the choice that yields the minimum amount to you.
Therefore, the maximum amount you can get when you choose Ai is:
Case 2 - If you pick coin Aj :
Similar to case 1, the maximum amount you can get when you choose Aj is:
Now we have the recursive function in hand and the above recurrence relation could be implemented in few lines of code. But if we follow simple recursion, its complexity is exponential. The reason is that each recursive call branches into a total of four separate recursive calls, and it could be n levels deep from the very first call).
To reduce the time complexity, we need to store the intermediate values in a table and use them, when required. Memoization provides an efficient way by avoiding re-computations using intermediate results stored in a table.
If you notice, we do not need to build complete matrix but only need to build right (upper) triangular matrix. Also notice that C[i, i] (diagonal elements) will always be equal to A[i]. Below is the code which runs in O(n^2) time and takes O(n^2) space.
Answer: If we assume that n is even then there is simple strategy to ensure that you win. Find the sum of coins at even and odd positions say X and Y respectively. Now if X>Y, pick coins only at even positions else pick coins only at odd positions.
How to ensure that you pick coins only at even or odd positions:
Assume that n = 6 and value are {3, 2, 2, 3, 1, 2}. Then sum at even positions is 7 (2 +3 +2) and at odd positions is 6 (3 +2 +1). So you will always pick the coins at even positions to ensure wining. How to achieve it:
Is above strategy optimal:
Above strategy ensure you winning but doesn’t ensure that you are drawing the maximum amount of money. For previous example {3, 2, 2, 3, 1, 2}, you drew a sum of 7, but you could have drawn a sum of 8. Lets see how:
Pick the first coin (3) instead. The opponent is left with two possible choices, the left coin (2) and the right coin (2), both valued at 2. No matter, which coin the opponent chose, you can always take the other coin (2) next and the configuration of the coins becomes: {2, 3, 1}. Now, the coin in the middle (3) would be yours to keep for sure. Therefore, you win the game by a total amount of 3 + 2 + 3 = 8, which proves that the previous non-losing strategy is not necessarily optimal.
Since your opponent is as smart as you, so we need to take all the possible combinations in consideration before our move. Calculating all those moves will include repetitive calculations so this is the place where we can use Dynamic Programming to show its magic.
Say coins are in stored in array A and C(i, j) is the maximum sum possible when remaining coins are from i to j. Lets discuss the case when there are remaining coins from i to j (positions) and it’s your turn to pick the coin. Now there are 2 possibilities:
- You pick coin A(i)
- You pick coin A(j)
Therefore, the maximum amount you can get when you choose Ai is:
C1 = Ai + min { C(i+2, j), C(i+1, j-1) }
Case 2 - If you pick coin Aj :
Similar to case 1, the maximum amount you can get when you choose Aj is:
ThereforeC2 = Aj + min { C(i+1, j-1), C(i, j-2) }
C(i, j) = max { C1, C2 } = max { Ai + min { C(i+2, j), C(i+1, j-1) }, Aj + min { C(i+1, j-1), C(i, j-2) } }
Now we have the recursive function in hand and the above recurrence relation could be implemented in few lines of code. But if we follow simple recursion, its complexity is exponential. The reason is that each recursive call branches into a total of four separate recursive calls, and it could be n levels deep from the very first call).
To reduce the time complexity, we need to store the intermediate values in a table and use them, when required. Memoization provides an efficient way by avoiding re-computations using intermediate results stored in a table.
If you notice, we do not need to build complete matrix but only need to build right (upper) triangular matrix. Also notice that C[i, i] (diagonal elements) will always be equal to A[i]. Below is the code which runs in O(n^2) time and takes O(n^2) space.
#define MAX 100 int maxMoney(int A[], int N) { int C[MAX][MAX] = {0}; int x, y, z; //x = C[m+2][n], y = C[m+1][n-1], z = C[m][n-2] for (int i = 0; i < N; i++) { for (int m = 0, n = i; n < N; m++, n++) { //calculate x, y, z x = (m+2 < N) ? C[m+2][n] : 0; y = (m+1 < N && n-1 >= 0) ? C[m+1][n-1] : 0; z = (n-1 > 0) ? C[m][n-2] : 0; C[m][n] = max(A[m] + min(x,y), A[n] + min(y,z)); //For Debugging out.println(x + ", " + y + ", " + z); out.println(m + ", " + n << ", " + C[m][n]); } } return C[0][N-1]; }
0 comments:
Post a Comment