Stone Game

Updated on 25 June, 2025
Stone Game header image

Problem Statement

In this problem, Alice and Bob engage in a strategic game using an even number of piles of stones, each pile having a positive integer number of stones. The total sum of stones is odd, ensuring no possibility for a tie in the game. Alice kicks off the game, choosing to take a whole pile from either the beginning or the end of the row of stone piles. The game continues with alternating turns until no piles are left, and the player with the most stones wins. The challenge consists in determining if Alice or Bob will win, given both play the game optimally. The result is true if Alice wins under optimal play, otherwise false for Bob.

Examples

Example 1

Input:

piles = [5,3,4,5]

Output:

true

Explanation:

Alice starts first, and can only take the first 5 or the last 5.
Say she takes the first 5, so that the row becomes [3, 4, 5].
If Bob takes 3, then the board is [4, 5], and Alice takes 5 to win with 10 points.
If Bob takes the last 5, then the board is [3, 4], and Alice takes 4 to win with 9 points.
This demonstrated that taking the first 5 was a winning move for Alice, so we return true.

Example 2

Input:

piles = [3,7,2,3]

Output:

true

Constraints

  • 2 <= piles.length <= 500
  • piles.length is even.
  • 1 <= piles[i] <= 500
  • sum(piles[i]) is odd.

Approach and Intuition

The game involves dynamic strategy since each player aims to finish with the most stones while denying the other player high-value moves. Here's a breakdown of the approach:

  1. Player Alternation:

    • Alice and Bob take turns picking the best possible move from the start or end of the line of piles.
    • Being the first player (Alice in this scenario) often brings a strategic advantage because she always gets the first choice at optimizing her move.
  2. Strategic Move Evaluation:

    • The optimal play means that both Alice and Bob will look at the current situation and decide whether taking stones from the beginning or the end of the row maximizes their potential stone count towards the end of the game, while also potentially minimizing the next best move of the opponent.
  3. Immediate Maximization:

    • The players are not looking just at the immediate gain of stones but also at how their decision impacts the remaining piles.
    • The game thus evolves not just as a matter of maximum single-move gain but as a sequence of moves where each decision impacts future moves.
  4. No Draw Situation:

    • Given that the total number of stones is odd, the final score of the game cannot be equal, which simplifies the decision process to some degree as every move can potentially be decisive.

From the Examples

  • Example 1 illustrates a sequence where Alice makes an optimal choice that leads to sequences ensuring her victory regardless of Bob's decisions:

    • Alice starts by choosing a pile that not only maximizes her count but also positions the remaining piles in her favor.
    • Alice’s strategic forethought in pile selection allows her to maintain a lead through the subsequent turns.
  • Example 2 also shows a sequence that highlights how initial choices by Alice force Bob into making moves that eventually still result in a win for Alice, displaying the cascading effect of optimal initial choices.

This approach recognizes that the problem can be tackled by dynamic programming where the game evaluation involves foresight into future sequences influenced by current decisions, thus leveraging the game's constraints to systematically deduce the optimal plays.

Solutions

  • C++
  • Java
  • JavaScript
  • Python
cpp
class Solution {
public:
    bool canWin(vector<int>& stones) {
        int length = stones.size();

        int gameValue[length + 2][length + 2];
        memset(gameValue, 0, sizeof(gameValue));

        for (int size = 1; size <= length; ++size)
            for (int start = 0, end = size - 1; end < length; ++start, ++end) {
                int turn = (end + start + length) % 2;
                if (turn == 1)
                    gameValue[start + 1][end + 1] = max(stones[start] + gameValue[start + 2][end + 1], stones[end] + gameValue[start + 1][end]);
                else
                    gameValue[start + 1][end + 1] = min(-stones[start] + gameValue[start + 2][end + 1], -stones[end] + gameValue[start + 1][end]);
            }

        return gameValue[1][length] > 0;
    }
};

The provided C++ solution addresses the problem of determining if the first player can win a game where players take turns picking stones from either end of an array. The strategy uses a dynamic programming approach.

The function canWin within the Solution class evaluates whether the first player, given optimal play from both sides, can secure a victory. It accomplishes this using a 2D array gameValue to store intermediate results, ensuring each subproblem is only solved once, improving efficiency.

  • First, the solution initializes a 2D dynamic programming table gameValue using the memset function to set all values to zero.
  • Iterative computations fill in this table:
    • The outer for loop constructs solutions for subarrays of increasing size.
    • Pairs of inner for loops traverse these subarrays, computing the optimal game value for every possible segment of stones from start to end indices.
    • The game strategy alternates turns based on the combined position and size of the current segment, using modulo operation to determine the player's turn (turn).
    • On each player's turn, the strategy compares taking a stone from the start or the end of the current segment:
      • If it's the first player's turn (turn == 1), they aim to maximize their score by choosing the option that gives the higher result.
      • If it's the second player's turn (turn == 0), they aim to minimize the first player's score.
  • The final decision, whether the first player can win or not, is determined by the value at gameValue[1][length], which represents the game's outcome when considering the entire array of stones starting with the first player.

By the end of processing, if gameValue[1][length] is positive, the first player can guarantee a victory with optimal play. This decision-making process leverages both game theory and dynamic programming principles to effectively determine the winner.

java
class Solution {
    public boolean stoneGame(int[] stones) {
        int length = stones.length;

        int[][] gameState = new int[length + 2][length + 2];
        for (int size = 1; size <= length; ++size)
            for (int start = 0; start + size <= length; ++start) {
                int end = start + size - 1;
                int turn = (end + start + length) % 2;
                if (turn == 1)
                    gameState[start + 1][end + 1] = Math.max(stones[start] + gameState[start + 2][end + 1], stones[end] + gameState[start + 1][end]);
                else
                    gameState[start + 1][end + 1] = Math.min(-stones[start] + gameState[start + 2][end + 1], -stones[end] + gameState[start + 1][end]);
            }

        return gameState[1][length] > 0;
    }
}

This solution summary explains the implementation of the "Stone Game" using Java. The algorithm plays out a game where players pick a stone from either end of an array of stones, aiming to maximize their score.

  • Start by defining the stoneGame method, which accepts an array of integers stones.
  • Determine the length of the array with int length = stones.length.
  • Initialize a two-dimensional array gameState to store computed scores for different game states, dimensioned at [length + 2][length + 2] to manage edge cases without additional conditions.
  • Use nested loops to iterate through possible game states. The outer loop iterates over different possible subarray sizes (size), while the inner loop chooses the starting point of these subarrays (start).
  • For each subarray defined by its start and end, calculate who's turn it is using (end + start + length) % 2. If it's the first player's turn (turn == 1), update the gameState array with the maximum possible score the current player can achieve by picking the beginning or end stone of the current subarray.
  • On the opponent's turn, update the gameState by choosing the move which would result in the minimal score gain for the opponent.
  • Finally, gameState[1][length] > 0 will return true if the first player can win, otherwise false indicating the second player wins with optimal plays from both sides.

This algorithm efficiently predicts the outcome of the Stone Game using dynamic programming, taking into account the optimal decisions each player can make at each step of the game. The solution builds up answers from smaller sub-problems, ensuring that every possibility is considered to determine the final outcome.

js
var playStoneGame = function(stones) {
    const totalStones = stones.length;
    const game = Array(totalStones + 2).fill(0).map(() => Array(totalStones + 2).fill(0));

    for (let length = 1; length <= totalStones; ++length)
        for (let start = 0, end = length - 1; end < totalStones; ++start, ++end) {
            let turn = (end + start + totalStones) % 2;
            if (turn == 1)
                game[start+1][end+1] = Math.max(stones[start] + game[start+2][end+1], stones[end] + game[start+1][end]);
            else
                game[start+1][end+1] = Math.min(-stones[start] + game[start+2][end+1], -stones[end] + game[start+1][end]);
        }

    return game[1][totalStones] > 0;
};

In the "Stone Game" solution written in JavaScript, a player tries to determine whether they can win the game by leveraging a dynamic programming approach with a careful strategy to maximize or minimize their score. Initially, the code sets up a two-dimensional array, game, where the dimensions are derived from the size of the stones array plus two. Each cell in this array will store the optimal outcome of scores for a given range of stones.

The solution iterates through all possible subsets of the stones array using nested loops, where the outer loop regulates the size of the subset and the inner loop defines the start and end of the subset. During each iteration:

  • Determine the current turn using the modulo operation, which alternatively assigns players to even and odd turns.
  • Update the game array based on the player's turn:
    • If it's player one's turn (turn equals 1), focus on maximizing the score by selecting either the first or the last stone of the subset and adding it to the score derived from the remaining subset.
    • If it's player two's turn, the aim is to minimize the score differential, which is done by choosing a stone that - after its removal - leaves the opponent with a smaller edge in the subsequent round.

After processing all possible subsets and combinations, the ultimate strategy decision for player one lies in the top-right cell of the game array, game[1][totalStones]. This value determines whether player one's optimal strategies will lead to a positive score, indicating a win if it's greater than 0. Thus, the function returns a Boolean indicating whether player one has a winning strategy based on the initial configuration of stones. This approach ensures that the algorithm evaluates all scenarios to decide the most favorable outcome efficiently.

python
from functools import lru_cache

class Solution:
    def stoneGame(self, stones):
        total = len(stones)

        @lru_cache(None)
        def calculate(i, j):
            if i > j: return 0
            player = (j - i - total) % 2
            if player == 1:  # first player
                return max(stones[i] + calculate(i+1,j), stones[j] + calculate(i,j-1))
            else:
                return min(-stones[i] + calculate(i+1,j), -stones[j] + calculate(i,j-1))

        return calculate(0, total - 1) > 0

The given Python code implements a solution for the "Stone Game". This problem involves two players picking stones arranged in a row, where each player aims to collect a maximum stone value. The players take turns, and in each turn, a player can pick a stone either from the start or the end of the row.

Key features of the implementation:

  • The solution utilizes a lru_cache decorator from the functools module, which memoizes the results of expensive function calls to optimize the recursive solution.
  • A nested function calculate(i, j) determines the optimal play for the subarray of stones from index i to j. The function evaluates the scenario where each player can pick either the first or last stone of the current subarray.
  • The player variable alternates between 1 and 0, depending on whose turn it is. This is calculated using modulo arithmetic on the indices difference.
    • For Player 1 (denoted by 1), the function uses a maximizing strategy where the player tries to maximize the score over the next recursive calls.
    • For Player 0, the function uses a minimizing strategy. Here, the calculations adjust scores as negatives to simulate minimizing the opponent's score.
  • The stoneGame function starts the recursion from the full range of the stone list (from index 0 to total-1) and returns whether the first player wins by checking if the result is positive (>0).

Usage:

To deploy this solution in practical scenarios:

  1. Define your array of stones each with a specific integer value.
  2. Instantiate the Solution class and use the stoneGame method by passing the stone list to determine if the first player (assuming optimal play) is guaranteed a win.

The approach ensures that complex scenarios are handled efficiently through memoization, drastically reducing the computation time needed to solve the game even for larger arrays.

Comments

No comments yet.