Leetcode打卡:棋盘上有效移动组合的数目

执行结果:通过

题目: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)可以沿直线或对角线移动任意步数。

解题思路如下:

  1. 定义数据结构
    • 使用 Movement 结构体来表示棋子的移动状态,包括起始位置、结束位置、当前位置、以及移动的方向(dx, dy)。
    • 使用 rookDirectionsbishopDirectionsqueenDirections 数组来定义每种棋子的移动方向。
  2. 初始化
    • initMovement 函数用于初始化棋子的移动状态。
    • reset 函数用于重置棋子的当前位置到起始位置。
    • stopped 函数用于判断棋子是否已到达结束位置。
    • advance 函数用于按步长移动棋子。
  3. 判断相遇
    • cross 函数用于判断两个棋子在移动过程中是否会相遇。它首先重置两个棋子的当前位置,然后按照它们的移动规则逐步移动,如果在任何一步两个棋子的位置相同,则表示它们相遇。
  4. 检查合法性
    • check 函数用于检查在添加一个新的棋子移动方案后,该方案是否与之前的棋子移动方案发生冲突(即是否有棋子相遇)。
  5. 深度优先搜索(DFS)
    • dfs 函数是核心递归函数,用于枚举所有可能的棋子移动组合。
    • 它首先处理每个棋子原地不动的情况。
    • 然后,对于每种棋子和每个方向,它枚举棋子在该方向上移动1到7步(假设棋盘是8x8的)的所有可能情况。
    • 对于每种情况,如果它不与之前的棋子移动方案冲突,则递归地继续处理下一个棋子。
    • 当所有棋子都被处理完毕时,找到一个合法的组合,结果计数加一。
  6. 主函数
    • countCombinations 函数是程序的入口,它初始化结果变量和 Movement 栈,然后调用 dfs 函数开始搜索。
    • 最后,返回计算得到的合法组合数量。

通过这种方式,程序能够枚举所有可能的棋子移动组合,同时确保任意两个棋子在移动过程中不会相遇,从而计算出所有合法的组合数量。

相关推荐
夏末秋也凉32 分钟前
力扣-数组-121 买卖股票的最佳时机
算法·leetcode
还是车万大佬1 小时前
C语言与ASCII码应用之简单加密
c语言·开发语言·算法
q567315232 小时前
利用Python实现Union-Find算法
android·python·算法
岸榕.2 小时前
551 灌溉
数据结构·c++·算法
浪前2 小时前
【算法】移除元素
开发语言·数据结构·算法
bachelores2 小时前
数据结构-图
数据结构·算法·图论
XuanRanDev3 小时前
【数据结构】 树的遍历:先序、中序、后序和层序
数据结构·算法·深度优先
专注API从业者4 小时前
如何处理获取到的淘宝评论数据以进行有效的商品品控?
大数据·开发语言·数据库·算法
高 朗4 小时前
【算法刷题】leetcode hot 100 滑动窗口
算法·leetcode·职场和发展·滑动窗口
柠石榴4 小时前
【练习】力扣 热题100 两数之和
开发语言·c++·算法·leetcode