LeetCode 2017 - Grid Game
The problem gives us a 2 x n grid where each cell contains some number of points. Two robots move across this grid one after another. Both robots start at the top-left corner (0, 0) and must reach the bottom-right corner (1, n - 1). The movement rules are very restrictive.
Difficulty: 🟡 Medium
Topics: Array, Matrix, Prefix Sum
Solution
LeetCode 2017 - Grid Game
Problem Understanding
The problem gives us a 2 x n grid where each cell contains some number of points. Two robots move across this grid one after another. Both robots start at the top-left corner (0, 0) and must reach the bottom-right corner (1, n - 1).
The movement rules are very restrictive. A robot may only move:
- Right, from
(r, c)to(r, c + 1) - Down, from
(r, c)to(r + 1, c)
Since the grid has only two rows, every valid path has a very important property: each robot moves right several times on the top row, then moves down exactly once, then continues moving right on the bottom row.
The first robot moves first and collects all points along its path. Every cell visited by the first robot is then set to 0. After that, the second robot moves on the modified grid and collects points from the remaining cells.
The objectives of the two robots are opposite:
- The first robot wants to minimize the second robot’s final score.
- The second robot wants to maximize its own score.
We must determine the score the second robot obtains if both robots play optimally.
The constraints are important:
ncan be as large as5 * 10^4- Each cell value can be up to
10^5
This means the total sum can become very large, so we must use 64-bit integers in languages like Go. The constraints also make brute force enumeration of all possible paths impractical.
An important observation is that because the grid has only two rows, the first robot’s path is completely determined by the column where it moves down from the top row to the bottom row.
Several edge cases are important:
n = 1, where both robots are forced onto the same single path.- Very large values, where integer overflow becomes possible in some languages.
- Cases where the optimal strategy is not locally obvious, because the first robot is minimizing the second robot’s maximum possible remaining score.
Approaches
Brute Force Approach
A brute force solution would try every possible column where the first robot can move down. For each such choice, we simulate the resulting grid after the first robot clears its path. Then we compute the maximum score the second robot can achieve on that modified grid.
Since the grid has only two rows, there are exactly n possible turning points for the first robot. For each turning point, we could recompute the remaining sums on the grid by scanning the rows again.
This approach works because it explicitly checks every valid strategy for the first robot and computes the resulting best response from the second robot. However, if implemented naively, each simulation costs O(n), and we do this n times, leading to O(n^2) complexity.
With n = 5 * 10^4, quadratic time is too slow.
Optimal Approach
The key insight is that once the first robot chooses a column c where it moves down, the remaining grid has a very predictable structure.
Suppose the first robot:
- Travels along the top row from column
0toc - Moves down at column
c - Travels along the bottom row from column
cton - 1
After this happens:
- The remaining cells in the top row are only the columns to the right of
c - The remaining cells in the bottom row are only the columns to the left of
c
The second robot can collect points from only one of these two regions effectively:
- Either the remaining top-right section
- Or the remaining bottom-left section
Therefore, if the first robot turns at column c, the second robot’s score becomes:
max(
remaining sum in top row after c,
remaining sum in bottom row before c
)
The first robot wants to minimize this value.
This transforms the problem into a prefix/suffix sum optimization problem.
We maintain:
topRemaining, the suffix sum of the top rowbottomCollected, the prefix sum of the bottom row
As we iterate through each possible turning column, we compute:
secondRobot = max(topRemaining, bottomCollected)
Then we minimize this over all choices.
This gives an O(n) solution.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Simulates every turning point and rescans rows each time |
| Optimal | O(n) | O(1) | Uses running prefix and suffix sums |
Algorithm Walkthrough
- Compute the total sum of the top row.
This represents the points still available on the top row before the first robot starts removing cells. 2. Initialize two running values.
topRemainingstarts as the total top-row sum.bottomCollectedstarts as0.
These represent:
- The points remaining in the top row to the right of the turning point.
- The points accumulated in the bottom row to the left of the turning point.
- Iterate through each column
c.
Treat column c as the point where the first robot moves down.
4. Remove the current top-row cell from topRemaining.
Since the first robot visits (0, c), that cell becomes unavailable to the second robot.
5. Compute the second robot’s best possible score.
The second robot chooses whichever side gives more points:
- Top-right remaining section
- Bottom-left accumulated section
So:
secondRobot = max(topRemaining, bottomCollected)
- Update the answer.
The first robot wants to minimize the second robot’s score:
answer = min(answer, secondRobot)
- Add the current bottom-row cell to
bottomCollected.
After considering column c, future turning points will leave this bottom cell available to the second robot.
8. Continue until all columns are processed.
9. Return the minimum value found.
Why it works
For any turning column c, the first robot removes:
- All top-row cells from
0throughc - All bottom-row cells from
cthroughn - 1
This leaves exactly two possible regions with points:
- Top row after
c - Bottom row before
c
The second robot can only maximize one of these regions, so its optimal score is the maximum of the two sums. Since the first robot chooses the turning point minimizing this value, checking all turning points guarantees the optimal answer.
Python Solution
from typing import List
class Solution:
def gridGame(self, grid: List[List[int]]) -> int:
n = len(grid[0])
top_remaining = sum(grid[0])
bottom_collected = 0
answer = float("inf")
for col in range(n):
top_remaining -= grid[0][col]
second_robot = max(top_remaining, bottom_collected)
answer = min(answer, second_robot)
bottom_collected += grid[1][col]
return answer
The implementation begins by computing the total sum of the top row. This allows us to track the remaining top-row points dynamically as we move from left to right.
Inside the loop, we first subtract the current top-row value because the first robot consumes that cell when turning at the current column.
Next, we compute the second robot’s best possible score. The second robot chooses whichever remaining region contains more points, so we take the maximum of:
top_remainingbottom_collected
We then minimize the overall answer because the first robot is trying to reduce the second robot’s maximum achievable score.
Finally, we add the current bottom-row value into bottom_collected, preparing for the next iteration.
The algorithm uses only a few variables and processes each column exactly once.
Go Solution
func gridGame(grid [][]int) int64 {
n := len(grid[0])
var topRemaining int64
for _, value := range grid[0] {
topRemaining += int64(value)
}
var bottomCollected int64
answer := topRemaining
for col := 0; col < n; col++ {
topRemaining -= int64(grid[0][col])
secondRobot := max(topRemaining, bottomCollected)
answer = min(answer, secondRobot)
bottomCollected += int64(grid[1][col])
}
return answer
}
func max(a, b int64) int64 {
if a > b {
return a
}
return b
}
func min(a, b int64) int64 {
if a < b {
return a
}
return b
}
The Go implementation follows exactly the same logic as the Python version. The most important difference is integer handling.
Since grid values may be large and the total sum can exceed 32-bit integer limits, all running sums are stored as int64.
Go does not provide built-in generic min and max functions for integers, so helper functions are implemented explicitly.
Worked Examples
Example 1
grid = [[2,5,4],
[1,5,1]]
Initial values:
topRemaining = 11
bottomCollected = 0
answer = inf
| Column | topRemaining after subtraction | bottomCollected | secondRobot = max(...) | answer |
|---|---|---|---|---|
| 0 | 9 | 0 | 9 | 9 |
| 1 | 4 | 1 | 4 | 4 |
| 2 | 0 | 6 | 6 | 4 |
Final answer:
4
Example 2
grid = [[3,3,1],
[8,5,2]]
Initial values:
topRemaining = 7
bottomCollected = 0
| Column | topRemaining after subtraction | bottomCollected | secondRobot | answer |
|---|---|---|---|---|
| 0 | 4 | 0 | 4 | 4 |
| 1 | 1 | 8 | 8 | 4 |
| 2 | 0 | 13 | 13 | 4 |
Final answer:
4
Example 3
grid = [[1,3,1,15],
[1,3,3,1]]
Initial values:
topRemaining = 20
bottomCollected = 0
| Column | topRemaining after subtraction | bottomCollected | secondRobot | answer |
|---|---|---|---|---|
| 0 | 19 | 0 | 19 | 19 |
| 1 | 16 | 1 | 16 | 16 |
| 2 | 15 | 4 | 15 | 15 |
| 3 | 0 | 7 | 7 | 7 |
Final answer:
7
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each column is processed exactly once |
| Space | O(1) | Only a few running variables are used |
The algorithm is linear because it scans the grid columns one time while maintaining running prefix and suffix sums. No additional arrays or data structures proportional to n are required.
Test Cases
from typing import List
class Solution:
def gridGame(self, grid: List[List[int]]) -> int:
n = len(grid[0])
top_remaining = sum(grid[0])
bottom_collected = 0
answer = float("inf")
for col in range(n):
top_remaining -= grid[0][col]
second_robot = max(top_remaining, bottom_collected)
answer = min(answer, second_robot)
bottom_collected += grid[1][col]
return answer
solution = Solution()
assert solution.gridGame([[2,5,4],[1,5,1]]) == 4 # Example 1
assert solution.gridGame([[3,3,1],[8,5,2]]) == 4 # Example 2
assert solution.gridGame([[1,3,1,15],[1,3,3,1]]) == 7 # Example 3
assert solution.gridGame([[1],[1]]) == 0 # Single column edge case
assert solution.gridGame([[1,2],[3,4]]) == 2 # Small grid
assert solution.gridGame([[10,1,1],[1,10,10]]) == 2 # Optimal early turn
assert solution.gridGame([[1,1,100],[50,50,1]]) == 100 # Large top suffix
assert solution.gridGame([[100000,100000],[100000,100000]]) == 100000 # Large values
assert solution.gridGame([[5,5,5,5],[5,5,5,5]]) == 10 # Uniform values
assert solution.gridGame([[20,3,20,17],[9,1,1,19]]) == 20 # Mixed values
Test Summary
| Test | Why |
|---|---|
[[2,5,4],[1,5,1]] |
Validates example 1 |
[[3,3,1],[8,5,2]] |
Validates example 2 |
[[1,3,1,15],[1,3,3,1]] |
Validates example 3 |
[[1],[1]] |
Tests minimum possible grid width |
[[1,2],[3,4]] |
Tests small basic scenario |
[[10,1,1],[1,10,10]] |
Tests optimal early turning point |
[[1,1,100],[50,50,1]] |
Tests dominance of top suffix |
[[100000,100000],[100000,100000]] |
Tests large integer handling |
[[5,5,5,5],[5,5,5,5]] |
Tests symmetric grid |
[[20,3,20,17],[9,1,1,19]] |
Tests irregular distribution |
Edge Cases
One important edge case is when n = 1. In this scenario, there is only a single column, so both robots are forced to follow exactly the same path. The first robot collects every cell immediately, leaving nothing for the second robot. The implementation handles this naturally because topRemaining becomes 0 before any comparison is made.
Another important edge case involves very large grid values. Since each cell may contain up to 10^5 points and there may be 5 * 10^4 columns, the total sum can become extremely large. Languages with fixed-width integers, such as Go, must use int64 to avoid overflow. The provided Go solution explicitly converts all values to int64.
A third important edge case occurs when the optimal turning point is near the beginning or near the end of the grid. Naive greedy reasoning may incorrectly assume the robot should balance both sides evenly. However, some grids are highly skewed, making an extreme turning point optimal. The algorithm correctly checks every possible turning column, ensuring the true minimum is always found.
A fourth subtle edge case appears when both remaining regions contain equal sums. In such situations, the second robot can achieve either value, but the result remains the same because we only care about the maximum obtainable score. The algorithm handles this automatically through the max(topRemaining, bottomCollected) computation.