2017. Grid Game
MediumDescription
給定
2 x n的矩陣grid,兩個機器人都從(0,0)出發到(1,n-1),只能向右或向下移動。第一個機器人先走,經過的格子歸零;第二個機器人再走,收集剩餘的點數。第一個機器人要最小化第二個機器人的得分,第二個機器人會最大化自己的得分。回傳第二個機器人的得分。
Example:
Input: grid = [[2,5,4],[1,5,1]] Output: 4 (第一個機器人選擇在 column 2 轉下,第二個機器人最多只能得到 4)
Intuition#
核心思路:第一個機器人一定在某個 column
c從第 0 行轉到第 1 行。轉折點確定後,第二個機器人只能選擇「第 0 行 c 右邊的部分」或「第 1 行 c 左邊的部分」中較大的那個。第一個機器人要最小化這個最大值。
- 第一個機器人的路徑形狀:沿第 0 行走到某個 column
c,轉下到第 1 行,再走到底 - 轉折後,第二個機器人的可選區域只有兩塊:上方的 suffix sum(column c+1 到 n-1)和下方的 prefix sum(column 0 到 c-1)
- 第二個機器人會取兩者的 max,第一個機器人要找使 max 最小的
c
Approaches#
1: Brute Force Prefix Sum#
- 概念: 預計算前綴和,枚舉每個轉折點
c,計算兩塊區域的和,取 max 後找全局 min - 時間複雜度:
O(n)- 前綴和 + 一次遍歷 - 空間複雜度:
O(n)- 前綴和陣列
class Solution {
fun gridGame(grid: Array<IntArray>): Long {
val n = grid[0].size
val topPrefix = LongArray(n + 1)
val botPrefix = LongArray(n + 1)
for (i in 0 until n) {
topPrefix[i + 1] = topPrefix[i] + grid[0][i]
botPrefix[i + 1] = botPrefix[i] + grid[1][i]
}
var result = Long.MAX_VALUE
for (c in 0 until n) {
val topRemaining = topPrefix[n] - topPrefix[c + 1] // 第 0 行 c+1..n-1
val botRemaining = botPrefix[c] // 第 1 行 0..c-1
val robot2Score = maxOf(topRemaining, botRemaining)
result = minOf(result, robot2Score)
}
return result
}
}⭐2: Running Sum (O(1) Space)#
- 概念: 不需要額外陣列,用 running sum 即時計算:
topSum初始化為第 0 行總和,botSum初始化為 0,遍歷每個轉折點時更新 - 時間複雜度:
O(n)- 一次遍歷 - 空間複雜度:
O(1)- 只用常數變數
class Solution {
fun gridGame(grid: Array<IntArray>): Long {
val n = grid[0].size
var topSum = 0L
for (v in grid[0]) topSum += v
var botSum = 0L
var result = Long.MAX_VALUE
for (c in 0 until n) {
topSum -= grid[0][c] // 第 0 行 c 右邊的 suffix sum
val robot2Score = maxOf(topSum, botSum)
result = minOf(result, robot2Score)
botSum += grid[1][c] // 第 1 行 c 左邊的 prefix sum
}
return result
}
}這題的陷阱是不能用 Greedy 讓第一個機器人走最大路徑。第一個機器人的目標是最小化對手的得分,不是最大化自己的得分。必須考慮轉折點對剩餘區域的影響。
🔑 Takeaways#
- Pattern: 2-row grid 的博弈問題 -> 轉折點枚舉 + Prefix/Suffix Sum
- 關鍵技巧: 理解第一個機器人的轉折決定了第二個機器人的可選區域,問題轉化為 min-max 優化;注意使用
Long避免 overflow