2056. Number of Valid Move Combinations On Chessboard


Explanation

In this problem, we are given a chessboard and some chess pieces. The board initially contains only one piece of each type, and their positions are given as input. We are tasked to find the number of unique configurations we can get by moving these pieces on the board according to their valid move rules.

For example, suppose we have the following input:



pieces = ["rook", "bishop"]
positions = [[1, 1], [2, 1]]

The initial configuration of the board will look like this:



R B . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .

where 'R' represents the rook at position (1, 1) and 'B' represents the bishop at position (2, 1).

We need to generate all possible moves for the given pieces and make the moves on the board. Then, we will calculate the unique configurations we can achieve from these moves.

Approach

The solution uses a Depth-First Search (DFS) approach to build all possible move combinations for the given pieces. After calculating these combinations, we can move the pieces on the board according to the moves and calculate the total unique configurations.

The DFS algorithm is implemented in the getCombMoves function. The function generates move combinations for each piece by adding all the valid moves of that piece in the current combination. It iterates through all the pieces and generates a move combination for each piece at depth 'i'.

After generating all move combinations, the solution uses the dfs function to explore these move combinations and calculate the total unique configurations. For each move combination, the solution checks whether the new board configuration is valid or not. A configuration is valid if all the pieces are within the board boundary (i.e., 1 <= x <= 8 and 1 <= y <= 8) and no two pieces are in the same position. If the configuration is valid, we continue exploring by moving the pieces in a depth-first manner and add the configuration in the answer set.

The dfs function uses a bitmask to represent the active pieces. The pieces that are masked as active will make their moves in this turn. In each step, it moves the active pieces according to the given move combination and checks if the new configuration is valid. If the configuration is valid, it stores the configuration in an unordered set and explores the possible next move combination.

The solution uses a hashing function (hash) to encode the board configuration into a unique integer value. This allows us to store the board configurations in an unordered set and efficiently calculate the number of unique configurations.

The time complexity of the.solution is O(29^4 * 6 * 7).

Example

Now, let's walk through an example to see how the approach works:

Suppose we have this input:


python
pieces = ["rook", "bishop"]
positions = [[1, 1], [2, 1]]
  1. First, we call the getCombMoves function with this input to generate all possible move combinations for the given pieces. The move combinations will be: [[(1, 0), (1, 1)], [(1, 0), (1, -1)], [(1, 0), (-1, 1)], [(1, 0), (-1, -1)], [(-1, 0), (1, 1)], [(-1, 0), (1, -1)], [(-1, 0), (-1, 1)], [(-1, 0), (-1, -1)], [(0, 1), (1, 1)], [(0, 1), (1, -1)], [(0, 1), (-1, 1)], [(0, 1), (-1, -1)], [(0, 0), (1, 1)], [(0, 0), (1, -1)], [(0, 0), (-1, 1)], [(0, 0), (-1, -1)]]

  2. Next, we call the dfs function with the generated move combinations and the initial board configuration. The function will consider all the move combinations one by one and explore the new configurations.

  3. For each move combination, the dfs function moves the active pieces on the board based on the bitmask. If the new configurations are valid, it continues exploring by making new moves.

  4. After exploring all possible move combinations using DFS, the function calculates the total number of unique configurations. In this example, the function will return the count value.

Solution


python
from typing import List
from itertools import product

class Solution:
    def countCombinations(self, pieces: List[str], positions: List[List[int]]) -> int:
        n = len(pieces)
        comb_moves = self.get_comb_moves(pieces)
        board = [(x - 1, y - 1) for x, y in positions]

        ans = set()
        for move in comb_moves:
            self.dfs(move, n, board, (1 << n) - 1, ans)

        return len(ans)

    def get_comb_moves(self, pieces: List[str]) -> List[List[tuple]]:
        pieces_move = [self.get_move(piece) for piece in pieces]
        return list(product(*pieces_move))

    def get_move(self, piece: str) -> List[tuple]:
        moves = {"rook": [(1, 0), (-1, 0), (0, 1), (0, -1)], 
                 "bishop": [(1, 1), (1, -1), (-1, 1), (-1, -1)],
                 "queen": [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)]}
        return moves[piece]

    def dfs(self, move: List[tuple], n: int, board: List[tuple], active_mask: int, ans: set):
        if active_mask == 0:
            return
        
        ans.add(self.hash(board))
        for next_active_mask in range(1, 1 << n):
            if not (active_mask & next_active_mask):
                continue
            
            # Make a copy of the board
            next_board = list(board)

            # Move active pieces
            for i in range(n):
                if (next_active_mask >> i) & 1:
                    next_board[i] = (next_board[i][0] + move[i][0], next_board[i][1] + move[i][1])

            # Check the validity of the new configuration
            if self.is_valid(next_board):
                self.dfs(move, n, next_board, next_active_mask, ans)

    def is_valid(self, board: List[tuple]) -> bool:
        # Check boundary and uniquness of the positions
        positions = set()
        for x, y in board:
            if x < 0 or x >= 8 or y < 0 or y >= 8:
                return False
            if (x, y) in positions:
                return False
            positions.add((x, y))
        return True

    def hash(self, board: List[tuple]) -> int:
        hashed = 0
        for x, y in board:
            hashed = hashed * 64 + x * 8 + y
        return hashed

The solution is implemented in Python, but the same approach can be used and implemented in other programming languages like Java, JavaScript, C++, or C#.# Solution in JavaScript


javascript
class Solution {
    countCombinations(pieces, positions) {
        const n = pieces.length;
        const comb_moves = this.getCombMoves(pieces);
        const board = positions.map(([x, y]) => [x - 1, y - 1]);

        const ans = new Set();
        for (const move of comb_moves) {
            this.dfs(move, n, board, (1 << n) - 1, ans);
        }

        return ans.size;
    }

    getCombMoves(pieces) {
        const pieces_move = pieces.map(piece => this.getMove(piece));
        return product(...pieces_move);
    }

    getMove(piece) {
        const moves = {
            "rook": [[1, 0], [-1, 0], [0, 1], [0, -1]],
            "bishop": [[1, 1], [1, -1], [-1, 1], [-1, -1]],
            "queen": [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]]
        };
        return moves[piece];
    }

    dfs(move, n, board, active_mask, ans) {
        if (active_mask === 0) {
            return;
        }

        ans.add(this.hash(board));
        for (let next_active_mask = 1; next_active_mask < (1 << n); next_active_mask++) {
            if (!(active_mask & next_active_mask)) {
                continue;
            }

            const next_board = [...board]; 

            for (let i = 0; i < n; i++) {
                if ((next_active_mask >> i) & 1) {
                    next_board[i] = [next_board[i][0] + move[i][0], next_board[i][1] + move[i][1]];
                }
            }

            if (this.isValid(next_board)) {
                this.dfs(move, n, next_board, next_active_mask, ans);
            }
        }
    }

    isValid(board) {
        const positions = new Set();
        for (const [x, y] of board) {
            if (x < 0 || x >= 8 || y < 0 || y >= 8) {
                return false;
            }

            const key = x * 8 + y;
            if (positions.has(key)) {
                return false;
            }

            positions.add(key);
        }

        return true;
    }

    hash(board) {
        let hashed = 0;
        for (const [x, y] of board) {
            hashed = hashed * 64 + x * 8 + y;
        }
        return hashed;
    }
}

function product(...arrays) {
    const f = (a, b) => [].concat(...a.map(x => b.map(y => [].concat(x, y))));
    return arrays.reduce(f, [[]]);
}

Solution in Java


java
import java.util.*;

class Solution {
    public int countCombinations(List<String> pieces, List<List<Integer>> positions) {
        int n = pieces.size();
        List<List<int[]>> comb_moves = getCombMoves(pieces);
        List<int[]> board = new ArrayList<>();
        for (List<Integer> pos : positions) {
            board.add(new int[] {pos.get(0) - 1, pos.get(1) - 1});
        }

        Set<Integer> ans = new HashSet<>();
        for (List<int[]> move : comb_moves) {
            dfs(move, n, board, (1 << n) - 1, ans);
        }

        return ans.size();
    }

    private List<List<int[]>> getCombMoves(List<String> pieces) {
        List<List<int[]>> pieces_move = new ArrayList<>();
        for (String piece : pieces) {
            pieces_move.add(getMove(piece));
        }

        return cartesianProduct(pieces_move);
    }

    private List<int[]> getMove(String piece) {
        Map<String, int[][]> moves = new HashMap<>();
        moves.put("rook", new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}});
        moves.put("bishop", new int[][] {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}});
        moves.put("queen", new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}});

        return Arrays.asList(moves.get(piece));
    }

    private void dfs(List<int[]> move, int n, List<int[]> board, int active_mask, Set<Integer> ans) {
        if (active_mask == 0) {
            return;
        }

        ans.add(hash(board));
        for (int i = 1; i < (1 << n); i++) {
            int next_active_mask = i;
            if ((active_mask & next_active_mask) == 0) {
                continue;
            }

            List<int[]> next_board = new ArrayList<>();
            for (int[] pos : board) {
                next_board.add(pos.clone());
            }

            for (int j = 0; j < n; j++) {
                if ((next_active_mask >> j) % 2 == 1) {
                    next_board.set(j, new int[] {next_board.get(j)[0] + move.get(j)[0], next_board.get(j)[1] + move.get(j)[1]});
                }
            }

            if (isValid(next_board)) {
                dfs(move, n, next_board, next_active_mask, ans);
            }
        }
    }

    private boolean isValid(List<int[]> board) {
        Set<Integer> positions = new HashSet<>();
        for (int[] pos : board) {
            int x = pos[0], y = pos[1];
            if (x < 0 || x >= 8 || y < 0 || y >= 8) {
                return false;
            }

            int key = x * 8 + y;
            if (positions.contains(key)) {
                return false;
            }

            positions.add(key);
        }

        return true;
    }

    private int hash(List<int[]> board) {
        int hashed = 0;
        for (int[] pos : board) {
            int x = pos[0], y = pos[1];
            hashed = hashed * 64 + x * 8 + y;
        }
        return hashed;
    }

    private static <T> List<List<T>> cartesianProduct(List<List<T>> lists) {
        List<List<T>> result = new ArrayList<>();
        if (lists.isEmpty()) {
            result.add(Collections.emptyList());
            return result;
        }

        List<T> firstList = lists.get(0);
        List<List<T>> subresult = cartesianProduct(lists.subList(1, lists.size()));

        for (T item : firstList) {
            for (List<T> subitem : subresult) {
                List<T> newItem = new ArrayList<>();
                newItem.add(item);
                newItem.addAll(subitem);
                result.add(newItem);
            }
        }

        return result;
    }
}

These solutions for JavaScript and Java use the same approach as the Python solution explained earlier. The main difference between the implementations is the syntax and how the data structures are used in each language.

It is important to understand the core idea of the approach, which is a Depth-First Search (DFS) to build all possible move combinations for the given pieces. Then, the solution calculates the total unique configurations by iterating through the move combinations and applying them to the board. The key is to implement the functions getCombMoves (to calculate all possible move combinations) and dfs (to explore the move combinations and calculate the unique configurations).

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator
Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!