Alice and Bob play a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones piles[i].
The objective of the game is to end with the most stones. The total number of stones across all the piles is odd, so there are no ties.
Alice and Bob take turns, with Alice starting first. Each turn, a player takes the entire pile of stones either from the beginning or from the end of the row. This continues until there are no more piles left, at which point the person with the most stones wins.
Assuming Alice and Bob play optimally, return true if Alice wins the game, or false if Bob wins.
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 <= 500piles.length is even.1 <= piles[i] <= 500sum(piles[i]) is odd.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
In the Stone Game, we want to see if the first player can always win by picking stones optimally. The brute force approach explores every possible sequence of stone selections that both players could make, essentially simulating all possible games.
Here's how the algorithm would work step-by-step:
def stone_game_brute_force(pile_of_stones):
def can_first_player_win(stones, first_player_score, second_player_score, turn):
if not stones:
# All stones have been taken, check scores
return first_player_score > second_player_score
if turn == 0:
# First player's turn
# Try taking the first stone
win_if_take_first = can_first_player_win(
stones[1:], first_player_score + stones[0], second_player_score, 1
)
# Try taking the last stone
win_if_take_last = can_first_player_win(
stones[:-1], first_player_score + stones[-1], second_player_score, 1
)
# First player wins if they win in all game scenarios
return win_if_take_first and win_if_take_last
else:
# Second player's turn
# Try taking the first stone
win_if_take_first = can_first_player_win(
stones[1:], first_player_score, second_player_score + stones[0], 0
)
# Try taking the last stone
win_if_take_last = can_first_player_win(
stones[:-1], first_player_score, second_player_score + stones[-1], 0
)
# If second player can always win return false
return win_if_take_first and win_if_take_last
# The first player wants to maximize their score.
return can_first_player_win(pile_of_stones, 0, 0, 0)The core idea is to recognize that each player aims to maximize their own score, assuming perfect play from both. This problem can be efficiently solved using a technique that remembers the best possible outcome for each section of the stones.
Here's how the algorithm would work step-by-step:
def stone_game(piles):
number_of_piles = len(piles)
# dp[i][j] stores the max advantage Alice can get from piles[i...j]
dynamic_programming_table = [[0] * number_of_piles for _ in range(number_of_piles)]
# Base case: single pile, Alice takes it
for i in range(number_of_piles):
dynamic_programming_table[i][i] = piles[i]
# Fill the table diagonally
for pile_length in range(2, number_of_piles + 1):
for i in range(number_of_piles - pile_length + 1):
j = i + pile_length - 1
# Alice chooses either the left or right pile
dynamic_programming_table[i][j] = max(
piles[i] - dynamic_programming_table[i + 1][j],
piles[j] - dynamic_programming_table[i][j - 1]
)
# Alice wins if her advantage is positive
# Determine who wins and return
return dynamic_programming_table[0][number_of_piles - 1] > 0| Case | How to Handle |
|---|---|
| Null or empty input array | Return false immediately as no stones can be taken. |
| Array with only one pile | Alex takes the only pile, so return true. |
| Array with two piles | Alex takes the larger pile; return true as Alex always wins with two piles. |
| Array with all piles having the same number of stones | Alex can still force a win if the total number of piles is odd, otherwise Lee can force a draw; calculate based on array length. |
| Array with a single very large pile compared to other small piles | Alex will take the large pile and try to maximize their gain; the optimal strategy must be calculated. |
| Large input array (performance) | Use dynamic programming to avoid exponential time complexity from naive recursion. |
| Integer overflow when summing pile values | Use long data type to store sums to prevent potential integer overflow. |
| Negative number of stones in a pile (invalid input) | Throw an IllegalArgumentException or return false, depending on problem specification. |