Hard
题目描述
在一个 50 x 50 的棋盘上有一个骑士和一些兵。给定两个整数 kx 和 ky,其中 (kx, ky) 表示骑士的位置,还有一个二维数组 positions,其中 positions[i] = [xi, yi] 表示棋盘上兵的位置。
Alice 和 Bob 进行回合制游戏,Alice 先手。在每个玩家的回合中:
- 玩家选择一个仍存在于棋盘上的兵,并用骑士以最少的移动次数捕获它。注意玩家可以选择任何兵,不一定是能够以最少移动次数捕获的兵。
- 在捕获选定兵的过程中,骑士可能经过其他兵而不捕获它们。在这个回合中只能捕获选定的兵。
Alice 试图最大化两个玩家在棋盘上没有更多兵之前所做的移动次数总和,而 Bob 试图最小化它们。
返回 Alice 在两个玩家都发挥最优的情况下能够实现的最大总移动次数。
注意在一次移动中,象棋骑士有八个可能的位置可以移动,如下所示。每次移动都是在基本方向上移动两个格子,然后在正交方向上移动一个格子。
示例 1:
输入:kx = 1, ky = 1, positions = [[0,0]]
输出:4
解释:骑士需要 4 步到达 (0, 0) 处的兵。
示例 2:
输入:kx = 0, ky = 2, positions = [[1,1],[2,2],[3,3]]
输出:8
解释:
- Alice 选择 (2, 2) 处的兵,用两步捕获:(0, 2) -> (1, 4) -> (2, 2)
- Bob 选择 (3, 3) 处的兵,用两步捕获:(2, 2) -> (4, 1) -> (3, 3)
- Alice 选择 (1, 1) 处的兵,用四步捕获:(3, 3) -> (4, 1) -> (2, 2) -> (0, 3) -> (1, 1)
示例 3:
输入:kx = 0, ky = 0, positions = [[1,2],[2,4]]
输出:3
解释:
- Alice 选择 (2, 4) 处的兵,用两步捕获:(0, 0) -> (1, 2) -> (2, 4)
- Bob 选择 (1, 2) 处的兵,用一步捕获:(2, 4) -> (1, 2)
约束条件:
- 0 <= kx, ky <= 49
- 1 <= positions.length <= 15
- positions[i].length == 2
- 0 <= positions[i][0], positions[i][1] <= 49
- 所有 positions[i] 都是唯一的
- 对于所有 0 <= i < positions.length,positions[i] != [kx, ky]
解题思路
这是一个博弈论问题,需要使用动态规划和位掩码来解决。
核心思路:
- 预处理距离:使用BFS计算从每个位置(包括骑士初始位置)到每个兵的最短距离
- 博弈DP:使用位掩码表示已被捕获的兵的状态,用DP来模拟最优博弈
- Minimax策略:Alice试图最大化总移动次数,Bob试图最小化
详细步骤:
- 首先将骑士初始位置也当作一个"位置",这样便于统一处理
- 对每个位置使用BFS计算到所有兵的最短距离
- 使用
dp[mask][pos]表示在状态mask下(mask表示哪些兵已被捕获),当前位置为pos时的最优结果 - Alice回合时选择能使总移动数最大的兵,Bob回合时选择能使总移动数最小的兵
- 状态转移时枚举下一个要捕获的兵,累加对应的移动距离
位掩码技巧:
- 使用整数的二进制位表示兵的捕获状态
mask & (1 << i)检查第i个兵是否已被捕获mask | (1 << i)表示捕获第i个兵后的状态
时间复杂度主要由状态数决定:O(n * 2^n * 50^2),其中BFS预处理和DP状态转移都需要考虑。
代码实现
class Solution {
public:
int maxMoves(int kx, int ky, vector<vector<int>>& positions) {
int n = positions.size();
// 将骑士位置加入positions数组的开头
positions.insert(positions.begin(), {kx, ky});
// BFS计算任意两点间的最短距离
vector<vector<int>> dist(n + 1, vector<int>(n + 1));
int dx[] = {-2, -2, -1, -1, 1, 1, 2, 2};
int dy[] = {-1, 1, -2, 2, -2, 2, -1, 1};
for (int i = 0; i <= n; i++) {
vector<vector<int>> d(50, vector<int>(50, -1));
queue<pair<int, int>> q;
q.push({positions[i][0], positions[i][1]});
d[positions[i][0]][positions[i][1]] = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int k = 0; k < 8; k++) {
int nx = x + dx[k];
int ny = y + dy[k];
if (nx >= 0 && nx < 50 && ny >= 0 && ny < 50 && d[nx][ny] == -1) {
d[nx][ny] = d[x][y] + 1;
q.push({nx, ny});
}
}
}
for (int j = 0; j <= n; j++) {
dist[i][j] = d[positions[j][0]][positions[j][1]];
}
}
// DP: dp[mask][pos] 表示在状态mask下,当前在pos位置的最优值
vector<vector<int>> dp(1 << n, vector<int>(n + 1, -1));
function<int(int, int, bool)> dfs = [&](int mask, int pos, bool isAlice) -> int {
if (mask == (1 << n) - 1) return 0; // 所有兵都被捕获
if (dp[mask][pos] != -1) return dp[mask][pos];
int result = isAlice ? 0 : INT_MAX;
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) continue; // 第i个兵已被捕获
int newMask = mask | (1 << i);
int cost = dist[pos][i + 1]; // +1因为positions[0]是骑士位置
int subResult = dfs(newMask, i + 1, !isAlice);
if (isAlice) {
result = max(result, cost + subResult);
} else {
result = min(result, cost + subResult);
}
}
return dp[mask][pos] = result;
};
return dfs(0, 0, true);
}
};
class Solution:
def maxMoves(self, kx: int, ky: int, positions: List[List[int]]) -> int:
from collections import deque
n = len(positions)
# 将骑士位置加入positions数组的开头
positions = [[kx, ky]] + positions
# BFS计算任意两点间的最短距离
dist = [[0] * (n + 1) for _ in range(n + 1)]
dx = [-2, -2, -1, -1, 1, 1, 2, 2]
dy = [-1, 1, -2, 2, -2, 2, -1, 1]
for i in range(n + 1):
d = [[-1] * 50 for _ in range(50)]
queue = deque([(positions[i][0], positions[i][1])])
d[positions[i][0]][positions[i][1]] = 0
while queue:
x, y = queue.popleft()
for k in range(8):
nx, ny = x + dx[k], y + dy[k]
if 0 <= nx < 50 and 0 <= ny < 50 and d[nx][ny] == -1:
d[nx][ny] = d[x][y] + 1
queue.append((nx, ny))
for j in range(n + 1):
dist[i][j] = d[positions[j][0]][positions[j][1]]
# DP with memoization
dp = {}
def dfs(mask, pos, is_alice):
if mask == (1 << n) - 1: # 所有兵都被捕获
return 0
if (mask, pos, is_alice) in dp:
return dp[(mask, pos, is_alice)]
result = 0 if is_alice else float('inf')
for i in range(n):
if mask & (1 << i): # 第i个兵已被捕获
continue
new_mask = mask | (1 << i)
cost = dist[pos][i + 1] # +1因为positions[0]是骑士位置
sub_result = dfs(new_mask, i + 1, not is_alice)
if is_alice:
result = max(result, cost + sub_result)
else:
result = min(result, cost + sub_result)
dp[(mask, pos, is_alice)] = result
return result
return dfs(0, 0, True)
public class Solution {
public int MaxMoves(int kx, int ky, int[][] positions) {
int n = positions.Length;
// 将骑士位置加入positions数组的开头
int[][] allPositions = new int[n + 1][];
allPositions[0] = new int[] {kx, ky};
for (int i = 0; i < n; i++) {
allPositions[i + 1] = positions[i];
}
// BFS计算任意两点间的最短距离
int[,] dist = new int[n + 1, n + 1];
int[] dx = {-2, -2, -1, -1, 1, 1, 2, 2};
int[] dy = {-1, 1, -2, 2, -2, 2, -1, 1};
for (int i = 0; i <= n; i++) {
int[,] d = new int[50, 50];
for (int x = 0; x < 50; x++) {
for (int y = 0; y < 50; y++) {
d[x, y] = -1;
}
}
Queue<(int, int)> queue = new Queue<(int, int)>();
queue.Enqueue((allPositions[i][0], allPositions[i][1]));
d[allPositions[i][0], allPositions[i][1]] = 0;
while (queue.Count > 0) {
var (x, y) = queue.Dequeue();
for (int k = 0; k < 8; k++) {
int nx = x + dx[k];
int ny = y + dy[k];
if (nx >= 0 && nx < 50 && ny >= 0 && ny < 50 && d[nx, ny] == -1) {
d[nx, ny] = d[x, y] + 1;
queue.Enqueue((nx, ny));
}
}
}
for (int j = 0; j <= n; j++) {
dist[i, j] = d[allPositions[j][0], allPositions[j][1]];
}
}
// DP with memoization
Dictionary<(int, int, bool), int> memo = new Dictionary<(int, int, bool), int>();
int Dfs(int mask, int pos, bool isAlice) {
if (mask == (1 << n) - 1) return 0; // 所有兵都被捕获
if (memo.ContainsKey((mask, pos, isAlice))) {
return memo[(mask, pos, isAlice)];
}
int result = isAlice ? 0 : int.MaxValue;
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) continue; // 第i个兵已被捕获
int newMask = mask | (1 << i);
int cost = dist[pos, i + 1]; // +1因为allPositions[0]是骑士位置
int subResult = Dfs(newMask, i + 1, !isAlice);
if (isAlice) {
result = Math.Max(result, cost + subResult);
} else {
result = Math.Min(result, cost + subResult);
}
}
memo[(mask, pos, isAlice)] = result;
return result;
}
return Dfs(0, 0, true);
}
}
var maxMoves = function(kx, ky, positions) {
const n = positions.length;
const moves = [[2,1],[2,-1],[-2,1],[-2,-1],[1,2],[1,-2],[-1,2],[-1,-2]];
// Calculate shortest distance between any two positions using BFS
function bfs(startX, startY, endX, endY) {
if (startX === endX && startY === endY) return 0;
const queue = [[startX, startY, 0]];
const visited = new Set();
visited.add(`${startX},${startY}`);
while (queue.length > 0) {
const [x, y, dist] = queue.shift();
for (const [dx, dy] of moves) {
const nx = x + dx;
const ny = y + dy;
const key = `${nx},${ny}`;
if (nx >= 0 && nx < 50 && ny >= 0 && ny < 50 && !visited.has(key)) {
if (nx === endX && ny === endY) return dist + 1;
visited.add(key);
queue.push([nx, ny, dist + 1]);
}
}
}
return Infinity;
}
// Precompute distances from knight position to all pawns and between all pairs of pawns
const dist = Array(n + 1).fill(null).map(() => Array(n + 1).fill(0));
// Distance from knight to each pawn
for (let i = 0; i < n; i++) {
dist[n][i] = bfs(kx, ky, positions[i][0], positions[i][1]);
}
// Distance between each pair of pawns
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (i !== j) {
dist[i][j] = bfs(positions[i][0], positions[i][1], positions[j][0], positions[j][1]);
}
}
}
// DP with memoization
const memo = new Map();
function dp(mask, lastPos, isAlice) {
const key = `${mask},${lastPos},${isAlice}`;
if (memo.has(key)) return memo.get(key);
if (mask === (1 << n) - 1) return 0;
let result = isAlice ? -Infinity : Infinity;
for (let i = 0; i < n; i++) {
if (!(mask & (1 << i))) {
const newMask = mask | (1 << i);
const moveCost = lastPos === n ? dist[n][i] : dist[lastPos][i];
const totalCost = moveCost + dp(newMask, i, !isAlice);
if (isAlice) {
result = Math.max(result, totalCost);
} else {
result = Math.min(result, totalCost);
}
}
}
memo.set(key, result);
return result;
}
return dp(0, n, true);
};
复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | - |
| 空间 | - |
相关题目
. Knight Probability in Chessboard (Medium)
. Check Knight Tour Configuration (Medium)