【Leetcode】2056. 棋盘上有效移动组合的数目——2611

题目

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 开始移动。如果在某个时刻,两个或者更多棋子占据了同一个格子,那么这个移动组合 不有效

请你返回 有效 移动组合的数目。

注意:

  • 初始时,不会有两个棋子同一个位置 。
  • 有可能在一个移动组合中,有棋子不移动。
  • 如果两个棋子 直接相邻 且两个棋子下一秒要互相占据对方的位置,可以将它们在同一秒内 交换位置

提示:

  • n == pieces.length
  • n == positions.length
  • 1 <= n <= 4
  • pieces 只包含字符串 "rook""queen""bishop"
  • 棋盘上最多只有一个后。
  • 1 <= ri, ci <= 8
  • 每一个 positions[i] 互不相同。# 2056. 棋盘上有效移动组合的数目

思路

首先,题目要求我们计算棋盘上所有可能的“有效”移动组合。棋盘上有多种棋子(车、后、象),每个棋子可以在指定的规则下移动,每个棋子最多移动一次。我们需要确保在任何时刻,棋盘上的两个棋子不能占据相同的位置。

核心问题在于:

  • 每个棋子的移动规则不同:车只能水平和竖直移动,后可以水平、竖直和对角线移动,而象只能对角线移动。
  • 确保棋子之间不会发生冲突:即同一时刻不能有两个棋子占据相同的格子。

为了计算有效的移动组合,我们需要遍历所有棋子在所有可能位置的组合。由于题目中棋子的数量最多为 4,因此,暴力枚举所有可能的棋子移动组合是可以接受的。

关键点:

  • 每个棋子的移动是独立的:每个棋子有自己独立的可选移动位置。
  • 回溯策略:我们需要通过递归(深度优先搜索)遍历每个棋子的所有可选位置,并在遇到无效组合时回溯,尝试其他可能的路径。
  • 冲突检测:每次移动后,我们需要检查当前状态是否有效,确保没有两个棋子占据同一个位置。

选择棋子不移动:处理当前棋子不动的情况

在处理每个棋子时,我们首先要判断是否可以让当前棋子停在原位置不动。为了防止两个棋子重叠在同一个位置,我们需要确保当前棋子的原位置没有被前面的棋子占据。

if all(pos[i][0] * 9 + pos[i][1] not in had[j] for j in range(i)):
    last.append(pos[i][0] * 9 + pos[i][1])  # 标记当前棋子的位置
    res += dfs(i + 1)  # 继续递归下一个棋子
    last.pop()  # 回溯,撤销这个操作

这里使用了 all 判断当前棋子的位置是否没有被其他棋子占据。如果没有被占据,我们将当前位置添加到 last 中,然后递归处理下一个棋子,最后再回溯,撤销这次选择。

选择棋子移动:处理当前棋子选择移动的情况

如果棋子选择移动,我们需要遍历它的所有可能移动方向。对于每个方向,我们逐步检查每一步的可行性,直到该棋子无法继续移动为止。每次尝试移动一个格子时,我们需要进行冲突检查,确保当前移动不会导致棋子碰撞。

for dr, dc in dxy[pieces[i]]:
    t = []
    r, c = pos[i]
    step = 0  # 走的步数
    while 8 >= r + dr >= 1 and 8 >= c + dc >= 1:  # 判断是否越界
        r += dr
        c += dc
        step += 1  # 增加步数

确保当前移动没有冲突

在每一步移动时,我们需要确保当前的格子没有被其他棋子占据,或者没有其他棋子阻挡着路径。我们需要检查以下几种情况:

  1. 该位置在当前棋子移动时已经被其他棋子占据;
  2. 当前棋子走到的位置时其他棋子也恰好走到这里,无法继续移动。
f = 0
for j in range(i):
    if r * 9 + c == last[j] and had[j][r * 9 + c] <= step:
        f = 1
        break
    if r * 9 + c in had[j] and had[j][r * 9 + c] == step:
        f = 1
        break

在每次递归结束后,我们需要撤销当前棋子的移动,以便尝试其他的可能路径。
复杂度与性能
由于每个棋子最多有 15 种可选的移动方式(对于车而言),并且最多 4 个棋子,我们的方案的时间复杂度大约为 \(O(15^n)\),其中 n 为棋子的数量。由于$ n <= 4$,这个复杂度在题目给定的约束下是可以接受的。

代码

dxy = {
    "queen": [(1, 1), (1, -1), (-1, 1), (-1, -1), (0, 1), (0, -1), (1, 0), (-1, 0)],
    "bishop": [(1, 1), (1, -1), (-1, 1), (-1, -1)],
    "rook": [(0, 1), (0, -1), (1, 0), (-1, 0)]
}
class Solution:
    def countCombinations(self, pieces: List[str], pos: List[List[int]]) -> int:

        n = len(pieces)  # 棋子的数量
        had = [dict() for _ in range(n)]
        # 设置初始棋子的位置
        # k 表示当前棋子到过这个位置
        # v 表示何时到达这个位置的
        for i, (r, c) in enumerate(pos):
            had[i][r * 9 + c] = 0

        last = []  # 棋子最终的位置

        def dfs(i):
            if i >= n: return 1
            res = 0
            # 当前棋子能否选择不移动?
            if all(pos[i][0] * 9 + pos[i][1] not in had[j] for j in range(i)):
                last.append(pos[i][0] * 9 + pos[i][1])
                res += dfs(i + 1)
                last.pop()
            # 当前棋子选择移动
            for dr, dc in dxy[pieces[i]]:
                t = list()
                r, c = pos[i]
                step = 0  # 行走的步数
                while 8 >= r + dr >= 1 and 8 >= c + dc >= 1:
                    r += dr
                    c += dc
                    step += 1  # 走了一步

                    # 如果当前位置当前时刻已经有棋子了
                    f = 0
                    for j in range(i):
                        # 1. 判断之前棋子放到这个位置了(前有停车,过不去)
                        if r*9+c == last[j] and had[j][r*9+c] <= step:
                            f = 1
                            break
                        #  2. 之前棋子在行进的过程中放到这个位置了(行进过程碰车)
                        if r*9+c in had[j] and had[j][r*9+c] == step:
                            f = 1
                            break
                    if f == 1: break
                    # 3. 若打算放到这个位置,还需要判断
                    #  如果停在这个位置,是否之前的就到达不了了
                    #  (若停车,是否阻碍其他人行驶?)

                    # 如果当前位置当前时刻没有棋子
                    had[i][r*9+c] = step
                    t.append(r*9+c)
                    if any(r*9+c in had[j] and had[j][r*9+c] >= step for j in range(i)):
                        continue
                    # 当前棋子放到了(r, c)位置
                    last.append(r*9+c)
                    res += dfs(i + 1)
                    last.pop()

                # 回溯
                for x in t:
                    del had[i][x]
            return res

        return dfs(0)
posted @ 2024-12-04 22:19  TICSMC  阅读(55)  评论(0)    收藏  举报