Game winning optimal strategy
1. Review


It's usually more convenient to return a result rather than printing it. Then you can use the result in other computations, for example test cases.
It's not clear what the meaning of the table t is. It would be helpful to have a comment. I think that if \$i ≤ j\$ then t[i][j] gets filled in with the maximum money obtainable by a player who starts with the coins \$v_i, \dots, v_j\$.
There is some trouble in handling edge cases — you need to avoid an invalid lookup in the table t so instead of writing t[i+1][j-1] you have to write:

0 if i+1 &gt; j-1 else t[i+1][j-1]


to avoid trouble in the cases i+1 == n and j == 0. What this suggests is that t would be better implemented as a mapping from pair (i, j) to money, using collections.defaultdict:

from collections import defaultdict
t = defaultdict(int)    # Map from (i,j) to max money on v[i:j+1].


Then you could look up any pair and it would default to zero even if it was out of bounds, like this:

x = t[i + 1, j - 1]
y = t[i + 2, j]
z = t[i, j - 2]
t[i, j] = max(v[i] + min(x, y), v[j] + min(x, z))

In this code:

for gap in xrange(n):
    for i in xrange(n):
        j = i + gap
        if j&lt;n:


you could avoid the test j&lt;n by iterating only over valid values of i:

for gap in xrange(n):
    for i in xrange(n - gap):
        j = i + gap

These lines of code:

x = t[i + 1, j - 1]
y = t[i + 2, j]
z = t[i, j - 2]
t[i, j] = max(v[i] + min(x, y), v[j] + min(x, z))


are looking two steps ahead (the next player chooses the minimum of their options, and the current player chooses the maximum of theirs).

But it is simpler to look only one step ahead, like this:

s = sum(v[i:j+1])
t[i, j] = s - min(t[i + 1, j], t[i, j - 1])


How does this work? Well, s is the sum of all the coins from i to j, so if the next player can win t[i + 1, j] (say), then the current player can win whatever's left.

(This is a standard trick when implementing a minimax algorithm. Instead of having different code for the even (max) and odd (min) players, use the same code for both players and negate the score at each step so that max and min swap roles.)

However, this is inefficient because of the computation of s, which requires iterating over all the coins from i to j. But we can avoid this by precomputing a list of running totals, using itertools.accumulate:

from itertools import accumulate
totals = [0] + list(accumulate(v))


and then the computation of s becomes:

s = totals[j + 1] - totals[i] # sum(v[i:j+1])



2. Revised code

from collections import defaultdict
from itertools import accumulate

def optimal_strategy_game(coins):
    """Return the maximum possible amount of money that can be won by a
    player starting on the given row of coins.
    """
    n = len(coins)
    totals = [0] + list(accumulate(coins)) # Running totals over coins
    money = defaultdict(int) # Map from (i,j) to max money on coins[i:j+1].
    for gap in range(n):
        for i in range(n - gap):
            j = i + gap
            s = totals[j + 1] - totals[i] # s = sum(coins[i:j+1])
            money[i, j] = s - min(money[i + 1, j], money[i, j - 1])
    return money[0, n - 1]


3. Alternative approach

The technique here (of building a table of solutions to sub-problems) is known as dynamic programming. But code that uses dynamic programming can always be rewritten to use recursion to visit the sub-problems and memoization to avoid duplicate work. Sometimes this results in clearer code. In Python 3.2 or later, many functions are easy to memoize using the @functools.lru_cache decorator:

from functools import lru_cache
from itertools import accumulate

def max_money(coins):
    """Return the maximum possible amount of money that can be won by a
    player starting on the given row of coins.
    """
    totals = [0] + list(accumulate(coins)) # Running totals over coins

    @lru_cache(maxsize=None)
    def money(start, stop):
        "Return maximum money that can be won on coins[start:stop]."
        if start &gt;= stop:
            return 0
        else:
            s = totals[stop] - totals[start] # s = sum(coins[start:stop])
            return s - min(money(start + 1, stop), money(start, stop - 1))

    return money(0, len(coins))


(Python 2.7 lacks lru_cache but there's a backport.)