Coins in a Line

There are n coins in a line. (Assume n is even). 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.

Would you rather go first or second? Does it matter?

Assume that you go first, describe an algorithm to compute the maximum amount of money you can win.

This is an interesting problem itself, and different solutions from multiple perspectives are provided in this post.

U.S. coins in various denominations in a line. Two players take turn to pick a coin from one of the ends until no more coins are left. Whoever with the larger amount of money wins.

Hints: If you go first, is there a strategy you can follow which prevents you from losing? Try to consider how it matters when the number of coins are odd vs. even.

Solution for (1): Going first will guarantee that you will not lose. By following the strategy below, you will always win the game (or get a possible tie).

Count the sum of all coins that are odd-numbered. (Call this X)

Count the sum of all coins that are even-numbered. (Call this Y)

If X > Y, take the left-most coin first. Choose all odd-numbered coins in subsequent moves.

If X < Y, take the right-most coin first. Choose all even-numbered coins in subsequent moves.

If X == Y, you will guarantee to get a tie if you stick with taking only even-numbered/odd-numbered coins.

You might be wondering how you can always choose odd-numbered/even-numbered coins. Let me illustrate this using an example where you have 10 coins:

If you take the coin numbered 1 (the left-most coin), your opponent can only have the choice of taking coin numbered 2 or 10 (which are both even-numbered coins). On the other hand, if you choose to take the coin numbered 10 (the right-most coin), your opponent can only take coin numbered 1 or 9 (which are odd-numbered coins).

Notice that the total number of coins change from even to odd and vice-versa when player takes turn each time. Therefore, by going first and depending on the coin you choose, you are essentially forcing your opponent to take either only even-numbered or odd-numbered coins.

Now that you have found a non-losing strategy, could you compute the maximum amount of money you can win?

Hints: One misconception is to think that the above non-losing strategy would generate the maximum amount of money as well. This is probably incorrect. Could you find a counter example? (You might need at least 6 coins to find a counter example).

Assume that you are finding the maximum amount of money in a certain range (ie, from coins numbered i to j, inclusive). Could you express it as a recursive formula? Find ways to make it as efficient as possible.

Solution for (2): Although the simple strategy illustrated in Solution (1) guarantees you not to lose, it does not guarantee that it is optimal in any way.

Here, we use a good counter example to better see why this is so. Assume the coins are laid out as below:

{ 3, 2, 2, 3, 1, 2 }

Following our previous non-losing strategy, we would count the sum of odd-numbered coins, X = 3 + 2 + 1 = 6, and the sum of even-numbered coins, Y = 2 + 3 + 2 = 7. As Y > X, we would take the last coin first and end up winning with the total amount of 7 by taking only even-numbered coins.

However, let us try another way by taking the first coin (valued at 3, denote by (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.

To solve this problem in an optimal way, we need to find efficient means in enumerating all possibilities. This is when Dynamic Programming (DP) kicks in and become so powerful that you start to feel magical.

First, we would need some observations to establish a recurrence relation, which is essential as our first step in solving DP problems.

The remaining coins are { Ai … Aj } and it is your turn. Let P(i, j) denotes the maximum amount of money you can get. Should you choose Ai or Aj?

Assume that P(i, j) denotes the maximum amount of money you can win when the remaining coins are { Ai, …, Aj }, and it is your turn now. You have two choices, either take Ai or Aj. First, let us focus on the case where you take Ai, so that the remaining coins become { Ai+1 … Aj }. Since the opponent is as smart as you, he must choose the best way that yields the maximum for him, where the maximum amount he can get is denoted by P(i+1, j).

In fact, we are able to simplify the above relation further to (Why?):

P(i, j) = Sum{Ai ... Aj} - min { P(i+1, j), P(i, j-1) }

Although the above recurrence relation is easy to understand, we need to compute the value of Sum{Ai … Aj} in each step, which is not very efficient. To avoid this problem, we can store values of Sum{Ai … Aj} in a table and avoid re-computations by computing in a certain order. Try to figure this out by yourself. (Hint: You would first compute P(1,1), P(2,2), … P(n, n) and work your way up).

A Better Solution: There is another solution which does not rely on computing and storing results of Sum{Ai … Aj}, therefore is more efficient in terms of time and space. Let us rewind back to the case where you take Ai, and the remaining coins become { Ai+1 … Aj }.

You took Ai from the coins { Ai … Aj }. The opponent will choose either Ai+1 or Aj. Which one would he choose?

Let us look one extra step ahead this time by considering the two coins the opponent will possibly take, Ai+1 and Aj. If the opponent takes Ai+1, the remaining coins are { Ai+2 … Aj }, which our maximum is denoted by P(i+2, j). On the other hand, if the opponent takes Aj, our maximum is P(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.

Although the above recurrence relation could be implemented in few lines of code, 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). Memoization provides an efficient way by avoiding re-computations using intermediate results stored in a table. Below is the code which runs in O(n2) time and takes O(n2) space.

Edit: Updated code with a new function printMoves which prints out all the moves you and the opponent make (assuming both of you are taking the coins in an optimal way).

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

constintMAX_N=100;

voidprintMoves(intP[][MAX_N],intA[],intN){

intsum1=0,sum2=0;

intm=0,n=N-1;

boolmyTurn=true;

while(m<=n){

intP1=P[m+1][n];// If take A[m], opponent can get...

intP2=P[m][n-1];// If take A[n]

cout<<(myTurn?"I":"You")<<" take coin no. ";

if(P1<=P2){

cout<<m+1<<" ("<<A[m]<<")";

m++;

}else{

cout<<n+1<<" ("<<A[n]<<")";

n--;

}

cout<<(myTurn?", ":".\n");

myTurn=!myTurn;

}

cout<<"\nThe total amount of money (maximum) I get is "<<P[0][N-1]<<".\n";

}

intmaxMoney(intA[],intN){

intP[MAX_N][MAX_N]={0};

inta,b,c;

for(inti=0;i<N;i++){

for(intm=0,n=i;n<N;m++,n++){

assert(m<N);assert(n<N);

a=((m+2<=N-1)?P[m+2][n]:0);

b=((m+1<=N-1&&n-1>=0)?P[m+1][n-1]:0);

c=((n-2>=0)?P[m][n-2]:0);

P[m][n]=max(A[m]+min(a,b),

A[n]+min(b,c));

}

}

printMoves(P,A,N);

returnP[0][N-1];

}

Further Thoughts: Assume that your opponent is so dumb that you are able to manipulate him into choosing the coins you want him to choose. Now, what is the maximum possible amount of money you can win?

@Anonymous:Thanks for your compliments. Feel free to work out the interview problems here and I'm sure you'll do good in your interview. Feel free to leave a comment if you have any questions, I would try my best to help.

@1337c0d3r: Thanks for your reply.Sorry for being late because I was busy with some interviews, but no success yet. I am getting many rejections. Is it possible for you to give me your email address so that I can discuss my problem with you. If you feel that it would be hard for you to give your email address here, I can share mine. Kindly let me know if it is convenient for you.

You are right. This method does not work when total number of coins is odd. In that case, the game is not meaningful anyway, since the player who goes first will have an unfair advantage of getting one less coin.

Hi, I find that your code is not efficient here and I check you code using the input in your post. Your code will use 21 calculation to get the result 8. I use the recursion to get the result with 9 calculation. Not sure whether my code is right. I paste here:

Essentially the number of calculations should be the same, the only difference is the top-down vs. bottom-up approach. I found that in your code, you only increase the counter when P2[i][j] == 0. If the index of i is out of bound, you return 0 without increasing the counter. Are you using the same method when comparing your code to my code? I would say the majority of extra counts you claimed is spent setting the values to 0.

Just use the the same function of printing the order. The sub problems that were not computed will not be referred to either in the print function. Also, your solution is indeed more efficient than the 1337′s, I think, which is also the advantage of recursion with memoization that only related sub problems are solved.

@speeddy: After further thoughts, your way to measure is misleading. Although your P matrix has fewer elements, setting P[][] takes four comparisons. The OP’s solution only takes two comparisons to set P[][]. Thus the number of elements in P matrix is not a good measurement.

At this time, P[2][3] has not been calculated yet, “a” would be zero. This is wrong because: 1. coin value are positive numbers. So in theory P[2][3] should definitely > 0. 2. your P[0][3] would be incorrect as a result of that because it fails to calculate the case when you take coin m and your opponent takes coin m-1.

I think the 1337′s codes used the bottom-up approach, and it starts with the sub problems with fewer coins in the range. So P[m+2][n] actually is computed before P[m][n] since there are fewer coins considered when compute P[m+2][n] than P[m][n].

@1337c0d3r: Thankx for posting the explanations for these questions. This is really helpful .

I am wondering if you can also start adding some comments in your code, because sometimes it might get difficult to understand it to some newbies. e.g: In this case, I am trying to understand how a 2-d array P is used and populated to lookup intermediate answers. I will post back with more specific query, but this is just a request.

This would be a solution using recursion but without memoization, which will have exponential time since many sub problems will be solved repeatedly. This is also why we use dynamic programming to avoid the duplicate solving of sub problems and to be more efficient.

The explanation is very clear. What an amazing work! Btw, I modified the code a little. Instead of calculating the whole matrix P[N][N], I only calculate half of it. The printmoves function is also modified. The code is listed below. Any comments are welcome. Thank you in advance!

A great solution indeed. I just wanted to ask one thing that what is the invariant outermost loop i is mantaining in the above. Like in the dp Matrix multiplication we have the outermost loop mantains the length of the matrix. Clearly inner loop m and n denote the left and right position to take the coin. And why is that the answer is in a[0][n-1] .. Magic ??

So, I have a similar problem for an assignment that I’ve been trying to work with. Can anyone shed some light on it? It’s the same problem, except each player can only pick 2 coins from one side in a row. Thanks! I get this solution, but I cannot figure out how to extend it at all.

it is a good problem and analysis and solution are perfect .but it is not logical.the opponent already knows that he will lose the game at first .there is no need for him to make reasonable decisions during the game,isn’t it? it is useless.so why we can assume that the opponent will try his best to make a good choice?

Interesting problem, but I wonder if this is a good question to ask in an interview. Would you expect someone to solve this problem in 45 mins along with coding and border scenarios in an efficient way ? Personally, I found it difficult to understand (not the concept), but the code. What do you guys think ?

The first solution that ensures winning will be useful only when the original total number of coins is even. If the total number of coins is odd, then you cannot force your opponent to only choose odd numbered or even numbered coins.