执行结果:通过
题目:2056 棋盘上有效移动组合的数目
有一个 8 x 8
的棋盘,它包含 n
个棋子(棋子包括车,后和象三种)。给你一个长度为 n
的字符串数组 pieces
,其中 pieces[i]
表示第 i
个棋子的类型(车,后或象)。除此以外,还给你一个长度为 n
的二维整数数组 positions
,其中 positions[i] = [ri, ci]
表示第 i
个棋子现在在棋盘上的位置为 (ri, ci)
,棋盘下标从 1 开始。
棋盘上每个棋子都可以移动 至多一次 。每个棋子的移动中,首先选择移动的 方向 ,然后选择 移动的步数 ,同时你要确保移动过程中棋子不能移到棋盘以外的地方。棋子需按照以下规则移动:
- 车可以 水平或者竖直 从
(r, c)
沿着方向(r+1, c)
,(r-1, c)
,(r, c+1)
或者(r, c-1)
移动。 - 后可以 水平竖直或者斜对角 从
(r, c)
沿着方向(r+1, c)
,(r-1, c)
,(r, c+1)
,(r, c-1)
,(r+1, c+1)
,(r+1, c-1)
,(r-1, c+1)
,(r-1, c-1)
移动。 - 象可以 斜对角 从
(r, c)
沿着方向(r+1, c+1)
,(r+1, c-1)
,(r-1, c+1)
,(r-1, c-1)
移动。
移动组合 包含所有棋子的 移动 。每一秒,每个棋子都沿着它们选择的方向往前移动 一步 ,直到它们到达目标位置。所有棋子从时刻 0
开始移动。如果在某个时刻,两个或者更多棋子占据了同一个格子,那么这个移动组合 不有效 。
请你返回 有效 移动组合的数目。
注意:
- 初始时,不会有两个棋子 在 同一个位置 。
- 有可能在一个移动组合中,有棋子不移动。
- 如果两个棋子 直接相邻 且两个棋子下一秒要互相占据对方的位置,可以将它们在同一秒内 交换位置 。
示例 1:
输入:pieces = ["rook"], positions = [[1,1]]
输出:15
解释:上图展示了棋子所有可能的移动。
示例 2:
输入:pieces = ["queen"], positions = [[1,1]]
输出:22
解释:上图展示了棋子所有可能的移动。
示例 3:
输入:pieces = ["bishop"], positions = [[4,3]]
输出:12
解释:上图展示了棋子所有可能的移动。
示例 4:
输入:pieces = ["rook","rook"], positions = [[1,1],[8,8]]
输出:223
解释:每个车有 15 种移动,所以总共有 15 * 15 = 225 种移动组合。
但是,有两个是不有效的移动组合:
- 将两个车都移动到 (8, 1) ,会导致它们在同一个格子相遇。
- 将两个车都移动到 (1, 8) ,会导致它们在同一个格子相遇。
所以,总共有 225 - 2 = 223 种有效移动组合。
注意,有两种有效的移动组合,分别是一个车在 (1, 8) ,另一个车在 (8, 1) 。
即使棋盘状态是相同的,这两个移动组合被视为不同的,因为每个棋子移动操作是不相同的。
示例 5:
输入:pieces = ["queen","bishop"], positions = [[5,7],[3,4]]
输出:281
解释:总共有 12 * 24 = 288 种移动组合。
但是,有一些不有效的移动组合:
- 如果后停在 (6, 7) ,它会阻挡象到达 (6, 7) 或者 (7, 8) 。
- 如果后停在 (5, 6) ,它会阻挡象到达 (5, 6) ,(6, 7) 或者 (7, 8) 。
- 如果象停在 (5, 2) ,它会阻挡后到达 (5, 2) 或者 (5, 1) 。
在 288 个移动组合当中,281 个是有效的。
提示:
n == pieces.length
n == positions.length
1 <= n <= 4
pieces
只包含字符串"rook"
,"queen"
和"bishop"
。- 棋盘上最多只有一个后。
1 <= ri, ci <= 8
- 每一个
positions[i]
互不相同。
代码以及解题思路
代码:
int rookDirections[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int bishopDirections[4][2] = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
int queenDirections[8][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
#define MAX_STACK_SIZE 1024
typedef struct {
int startX, startY, endX, endY, dx, dy, curX, curY;
} Movement;
void initMovement(Movement *obj, int startX, int startY, int endX, int endY, int dx, int dy) {
obj->startX = startX;
obj->startY = startY;
obj->endX = endX;
obj->endY = endY;
obj->dx = dx;
obj->dy = dy;
obj->curX = startX;
obj->curY = startX;
}
// Reset 重置棋子的当前位置
void reset(Movement *obj) {
obj->curX = obj->startX;
obj->curY = obj->startY;
}
// Stopped 判断棋子是否停止
bool stopped(Movement *obj) {
return obj->curX == obj->endX && obj->curY == obj->endY;
}
// Advance 让棋子按照步长移动
void advance(Movement *obj) {
if (!stopped(obj)) {
obj->curX += obj->dx;
obj->curY += obj->dy;
}
}
// Cross 判断两个棋子是否相遇
bool cross(Movement *m1, Movement *m2) {
// 每次判断是否相遇时需要重置 cur
reset(m1);
reset(m2);
while (!stopped(m1) || !stopped(m2)) {
advance(m1);
advance(m2);
if (m1->curX == m2->curX && m1->curY == m2->curY) {
return true;
}
}
return false;
}
// Check 判断第 u 个棋子是否与之前的棋子发生相交
bool check(int u, Movement *stack) {
for (int v = 0; v < u; v++) {
Movement *m1 = &stack[u], *m2 = &stack[v];
if (cross(m1, m2)) {
return false;
}
}
return true;
}
void dfs(int u, char** pieces, int piecesSize, int** positions, Movement *stack, int *res) {
if (u == piecesSize) {
(*res)++;
return;
}
// 处理第 u 个棋子原地不动的情况
initMovement(&stack[u], positions[u][0], positions[u][1], positions[u][0], positions[u][1], 0, 0);
if (check(u, stack)) {
dfs(u + 1, pieces, piecesSize, positions, stack, res);
}
// 枚举第 u 个棋子在所有方向、所有步数的情况
int (*directions)[2];
int directionsSize = 0;
if (!strcmp(pieces[u], "rook")) {
directions = rookDirections;
directionsSize = 4;
} else if (!strcmp(pieces[u], "queen")) {
directions = queenDirections;
directionsSize = 8;
} else {
directions = bishopDirections;
directionsSize = 4;
}
for (int i = 0; i < directionsSize; i++) {
for (int j = 1; j < 8; j++) {
int x = positions[u][0] + directions[i][0] * j;
int y = positions[u][1] + directions[i][1] * j;
if (x < 1 || x > 8 || y < 1 || y > 8) {
break;
}
initMovement(&stack[u], positions[u][0], positions[u][1], x, y, directions[i][0], directions[i][1]);
if (check(u, stack)) {
dfs(u + 1, pieces, piecesSize, positions, stack, res);
}
}
}
}
int countCombinations(char** pieces, int piecesSize, int** positions, int positionsSize, int* positionsColSize) {
int res = 0;
Movement stack[MAX_STACK_SIZE];
dfs(0, pieces, piecesSize, positions, stack, &res);
return res;
}
解题思路:
这个问题是关于在国际象棋棋盘上,给定一些棋子的类型和初始位置,计算它们移动到目标位置(这里假设目标位置是它们能移动到的任意合法位置,不一定是初始位置)的所有可能组合的数量,同时要求任意两个棋子在移动过程中不能相遇。棋子的类型包括车(rook)、象(bishop)和后(queen),每种棋子有不同的移动规则:
- 车(rook)可以沿直线水平或垂直移动任意步数。
- 象(bishop)可以沿对角线移动任意步数。
- 后(queen)可以沿直线或对角线移动任意步数。
解题思路如下:
- 定义数据结构 :
- 使用
Movement
结构体来表示棋子的移动状态,包括起始位置、结束位置、当前位置、以及移动的方向(dx, dy)。 - 使用
rookDirections
、bishopDirections
和queenDirections
数组来定义每种棋子的移动方向。
- 使用
- 初始化 :
initMovement
函数用于初始化棋子的移动状态。reset
函数用于重置棋子的当前位置到起始位置。stopped
函数用于判断棋子是否已到达结束位置。advance
函数用于按步长移动棋子。
- 判断相遇 :
cross
函数用于判断两个棋子在移动过程中是否会相遇。它首先重置两个棋子的当前位置,然后按照它们的移动规则逐步移动,如果在任何一步两个棋子的位置相同,则表示它们相遇。
- 检查合法性 :
check
函数用于检查在添加一个新的棋子移动方案后,该方案是否与之前的棋子移动方案发生冲突(即是否有棋子相遇)。
- 深度优先搜索(DFS) :
dfs
函数是核心递归函数,用于枚举所有可能的棋子移动组合。- 它首先处理每个棋子原地不动的情况。
- 然后,对于每种棋子和每个方向,它枚举棋子在该方向上移动1到7步(假设棋盘是8x8的)的所有可能情况。
- 对于每种情况,如果它不与之前的棋子移动方案冲突,则递归地继续处理下一个棋子。
- 当所有棋子都被处理完毕时,找到一个合法的组合,结果计数加一。
- 主函数 :
countCombinations
函数是程序的入口,它初始化结果变量和Movement
栈,然后调用dfs
函数开始搜索。- 最后,返回计算得到的合法组合数量。
通过这种方式,程序能够枚举所有可能的棋子移动组合,同时确保任意两个棋子在移动过程中不会相遇,从而计算出所有合法的组合数量。