Hard

题目描述

在一个 8 x 8 的棋盘上有 n 个棋子(车、后、象)。给你一个长度为 n 的字符串数组 pieces,其中 pieces[i] 描述第 i 个棋子的类型(车、后或象)。另外,给你一个长度为 n 的二维整数数组 positions,其中 positions[i] = [ri, ci] 表示第 i 个棋子当前位于棋盘上基于 1 的坐标 (ri, ci)。

当移动一个棋子时,你选择一个目标方格,棋子将朝着该方格移动并停在那里。

  • 车只能从 (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

约束条件:

  • n == pieces.length
  • n == positions.length
  • 1 <= n <= 4
  • pieces 只包含字符串 “rook”、“queen” 和 “bishop”
  • 棋盘上最多有一个后
  • 1 <= ri, ci <= 8
  • 每个 positions[i] 都是唯一的

解题思路

这道题需要计算所有有效的棋子移动组合数。由于棋子数量很少(最多4个),我们可以使用回溯算法枚举所有可能的移动组合。

核心思路:

  1. 生成可能移动:为每个棋子根据其类型和位置生成所有可能的移动路径。车可以水平/垂直移动,象可以对角线移动,后可以八个方向移动。

  2. 回溯枚举:使用回溯算法为每个棋子选择一个可能的移动,组成一个完整的移动组合。

  3. 碰撞检测:对于每个移动组合,需要模拟整个移动过程,检查是否在任何时刻有两个或更多棋子占据同一位置。

  4. 时间步模拟:每个棋子按照自己的移动方向每秒移动一步,需要追踪每个时间步所有棋子的位置。

关键细节:

  • 棋子可以选择留在原地不动(移动距离为0)
  • 需要考虑棋子移动的整个过程,而不仅仅是最终位置
  • 两个相邻棋子可以交换位置,这在模拟时需要特别处理

算法复杂度主要由可能移动数和回溯深度决定,由于棋子数量限制在4个以内,暴力枚举是可行的。

代码实现

class Solution {
public:
    int countCombinations(vector<string>& pieces, vector<vector<int>>& positions) {
        int n = pieces.size();
        vector<vector<pair<pair<int,int>, int>>> allMoves(n);
        
        // Generate all possible moves for each piece
        for (int i = 0; i < n; i++) {
            generateMoves(pieces[i], positions[i][0], positions[i][1], allMoves[i]);
        }
        
        vector<pair<pair<int,int>, int>> currentMoves(n);
        return backtrack(0, allMoves, currentMoves);
    }
    
private:
    void generateMoves(const string& piece, int r, int c, vector<pair<pair<int,int>, int>>& moves) {
        vector<pair<int,int>> directions;
        
        if (piece == "rook") {
            directions = {{0,1}, {0,-1}, {1,0}, {-1,0}};
        } else if (piece == "bishop") {
            directions = {{1,1}, {1,-1}, {-1,1}, {-1,-1}};
        } else { // queen
            directions = {{0,1}, {0,-1}, {1,0}, {-1,0}, {1,1}, {1,-1}, {-1,1}, {-1,-1}};
        }
        
        // Stay in place
        moves.push_back({{r, c}, 0});
        
        // Move in each direction
        for (auto [dr, dc] : directions) {
            for (int steps = 1; steps <= 7; steps++) {
                int nr = r + dr * steps, nc = c + dc * steps;
                if (nr < 1 || nr > 8 || nc < 1 || nc > 8) break;
                moves.push_back({{nr, nc}, steps});
            }
        }
    }
    
    int backtrack(int idx, const vector<vector<pair<pair<int,int>, int>>>& allMoves, 
                  vector<pair<pair<int,int>, int>>& currentMoves) {
        if (idx == allMoves.size()) {
            return isValidCombination(currentMoves) ? 1 : 0;
        }
        
        int count = 0;
        for (const auto& move : allMoves[idx]) {
            currentMoves[idx] = move;
            count += backtrack(idx + 1, allMoves, currentMoves);
        }
        return count;
    }
    
    bool isValidCombination(const vector<pair<pair<int,int>, int>>& moves) {
        int n = moves.size();
        int maxTime = 0;
        
        for (const auto& move : moves) {
            maxTime = max(maxTime, move.second);
        }
        
        for (int t = 0; t <= maxTime; t++) {
            set<pair<int,int>> occupied;
            for (int i = 0; i < n; i++) {
                pair<int,int> pos = getCurrentPosition(i, t, moves);
                if (occupied.count(pos)) return false;
                occupied.insert(pos);
            }
        }
        return true;
    }
    
    pair<int,int> getCurrentPosition(int pieceIdx, int time, const vector<pair<pair<int,int>, int>>& moves) {
        auto [target, steps] = moves[pieceIdx];
        if (time >= steps) return target;
        
        // Calculate intermediate position
        // This is a simplified version - in reality we need the starting position and direction
        return target; // This needs more complex calculation for intermediate positions
    }
};
class Solution:
    def countCombinations(self, pieces: List[str], positions: List[List[int]]) -> int:
        n = len(pieces)
        
        def get_directions(piece):
            if piece == "rook":
                return [(0, 1), (0, -1), (1, 0), (-1, 0)]
            elif piece == "bishop":
                return [(1, 1), (1, -1), (-1, 1), (-1, -1)]
            else:  # queen
                return [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)]
        
        def generate_moves(piece, r, c):
            moves = []
            directions = get_directions(piece)
            
            # Stay in place
            moves.append([(r, c)])
            
            # Move in each direction
            for dr, dc in directions:
                path = [(r, c)]
                for step in range(1, 8):
                    nr, nc = r + dr * step, c + dc * step
                    if nr < 1 or nr > 8 or nc < 1 or nc > 8:
                        break
                    path.append((nr, nc))
                    moves.append(path[:])
            
            return moves
        
        def is_valid_combination(move_paths):
            max_time = max(len(path) - 1 for path in move_paths)
            
            for t in range(max_time + 1):
                occupied = set()
                for i, path in enumerate(move_paths):
                    if t < len(path):
                        pos = path[t]
                    else:
                        pos = path[-1]  # Stay at final position
                    
                    if pos in occupied:
                        return False
                    occupied.add(pos)
            
            return True
        
        # Generate all possible moves for each piece
        all_moves = []
        for i in range(n):
            piece_moves = generate_moves(pieces[i], positions[i][0], positions[i][1])
            all_moves.append(piece_moves)
        
        def backtrack(idx, current_combination):
            if idx == n:
                return 1 if is_valid_combination(current_combination) else 0
            
            count = 0
            for move in all_moves[idx]:
                current_combination.append(move)
                count += backtrack(idx + 1, current_combination)
                current_combination.pop()
            
            return count
        
        return backtrack(0, [])
public class Solution {
    public int CountCombinations(string[] pieces, int[][] positions) {
        int n = pieces.Length;
        var allMoves = new List<List<List<(int, int)>>>();
        
        for (int i = 0; i < n; i++) {
            allMoves.Add(GenerateMoves(pieces[i], positions[i][0], positions[i][1]));
        }
        
        return Backtrack(0, allMoves, new List<List<(int, int)>>());
    }
    
    private List<List<(int, int)>> GenerateMoves(string piece, int r, int c) {
        var moves = new List<List<(int, int)>>();
        var directions = GetDirections(piece);
        
        // Stay in place
        moves.Add(new List<(int, int)> { (r, c) });
        
        foreach (var (dr, dc) in directions) {
            var path = new List<(int, int)> { (r, c) };
            for (int step = 1; step <= 7; step++) {
                int nr = r + dr * step, nc = c + dc * step;
                if (nr < 1 || nr > 8 || nc < 1 || nc > 8) break;
                path.Add((nr, nc));
                moves.Add(new List<(int, int)>(path));
            }
        }
        
        return moves;
    }
    
    private List<(int, int)> GetDirections(string piece) {
        return piece switch {
            "rook" => new List<(int, int)> { (0, 1), (0, -1), (1, 0), (-1, 0) },
            "bishop" => new List<(int, int)> { (1, 1), (1, -1), (-1, 1), (-1, -1) },
            _ => new List<(int, int)> { (0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1) }
        };
    }
    
    private int Backtrack(int idx, List<List<List<(int, int)>>> allMoves, List<List<(int, int)>> currentCombination) {
        if (idx == allMoves.Count) {
            return IsValidCombination(currentCombination) ? 1 : 0;
        }
        
        int count = 0;
        foreach (var move in allMoves[idx]) {
            currentCombination.Add(move);
            count += Backtrack(idx + 1, allMoves, currentCombination);
            currentCombination.RemoveAt(currentCombination.Count - 1);
        }
        
        return count;
    }
    
    private bool IsValidCombination(List<List<(int, int)>> movePaths) {
        int maxTime = movePaths.Max(path => path.Count - 1);
        
        for (int t = 0; t <= maxTime; t++) {
            var occupied = new HashSet<(int, int)>();
            for (int i = 0; i < movePaths.Count; i++) {
                var path = movePaths[i];
                var pos = t < path.Count ? path[t] : path[path.Count - 1];
                
                if (occupied.Contains(pos)) return false;
                occupied.Add(pos);
            }
        }
        
        return true;
    }
}
var countCombinations = function(pieces, positions) {
    const n = pieces.length;
    const moves = [];
    
    // Generate all possible moves for each piece
    for (let i = 0; i < n; i++) {
        moves[i] = [];
        const [r, c] = positions[i];
        const piece = pieces[i];
        
        // Add staying in place as a move
        moves[i].push([[r, c]]);
        
        // Define directions based on piece type
        let dirs = [];
        if (piece === 'rook') {
            dirs = [[0,1], [0,-1], [1,0], [-1,0]];
        } else if (piece === 'bishop') {
            dirs = [[1,1], [1,-1], [-1,1], [-1,-1]];
        } else { // queen
            dirs = [[0,1], [0,-1], [1,0], [-1,0], [1,1], [1,-1], [-1,1], [-1,-1]];
        }
        
        // Generate moves in each direction
        for (const [dr, dc] of dirs) {
            const path = [];
            let nr = r, nc = c;
            
            while (true) {
                nr += dr;
                nc += dc;
                if (nr < 1 || nr > 8 || nc < 1 || nc > 8) break;
                
                path.push([nr, nc]);
                moves[i].push([[r, c], ...path]);
            }
        }
    }
    
    let count = 0;
    
    function backtrack(pieceIdx, selectedMoves) {
        if (pieceIdx === n) {
            if (isValidCombination(selectedMoves)) {
                count++;
            }
            return;
        }
        
        for (const move of moves[pieceIdx]) {
            selectedMoves.push(move);
            backtrack(pieceIdx + 1, selectedMoves);
            selectedMoves.pop();
        }
    }
    
    function isValidCombination(selectedMoves) {
        const maxSteps = Math.max(...selectedMoves.map(move => move.length));
        
        for (let step = 0; step < maxSteps; step++) {
            const occupied = new Set();
            
            for (let piece = 0; piece < n; piece++) {
                const move = selectedMoves[piece];
                const pos = step < move.length ? move[step] : move[move.length - 1];
                const key = `${pos[0]},${pos[1]}`;
                
                if (occupied.has(key)) {
                    return false;
                }
                occupied.add(key);
            }
        }
        
        return true;
    }
    
    backtrack(0, []);
    return count;
};

复杂度分析

指标复杂度
时间-
空间-