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]]
-
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)]] -
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. -
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. -
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 EvaluatorProblem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!