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:
1 2 3pieces = ["rook", "bishop"] 4positions = [[1, 1], [2, 1]]
The initial configuration of the board will look like this:
1 2 3R B . . . . . . 4. . . . . . . . 5. . . . . . . . 6. . . . . . . . 7. . . . . . . . 8. . . . . . . . 9. . . . . . . . 10. . . . . . . .
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:
1 2python 3pieces = ["rook", "bishop"] 4positions = [[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
1
2python
3from typing import List
4from itertools import product
5
6class Solution:
7 def countCombinations(self, pieces: List[str], positions: List[List[int]]) -> int:
8 n = len(pieces)
9 comb_moves = self.get_comb_moves(pieces)
10 board = [(x - 1, y - 1) for x, y in positions]
11
12 ans = set()
13 for move in comb_moves:
14 self.dfs(move, n, board, (1 << n) - 1, ans)
15
16 return len(ans)
17
18 def get_comb_moves(self, pieces: List[str]) -> List[List[tuple]]:
19 pieces_move = [self.get_move(piece) for piece in pieces]
20 return list(product(*pieces_move))
21
22 def get_move(self, piece: str) -> List[tuple]:
23 moves = {"rook": [(1, 0), (-1, 0), (0, 1), (0, -1)],
24 "bishop": [(1, 1), (1, -1), (-1, 1), (-1, -1)],
25 "queen": [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)]}
26 return moves[piece]
27
28 def dfs(self, move: List[tuple], n: int, board: List[tuple], active_mask: int, ans: set):
29 if active_mask == 0:
30 return
31
32 ans.add(self.hash(board))
33 for next_active_mask in range(1, 1 << n):
34 if not (active_mask & next_active_mask):
35 continue
36
37 # Make a copy of the board
38 next_board = list(board)
39
40 # Move active pieces
41 for i in range(n):
42 if (next_active_mask >> i) & 1:
43 next_board[i] = (next_board[i][0] + move[i][0], next_board[i][1] + move[i][1])
44
45 # Check the validity of the new configuration
46 if self.is_valid(next_board):
47 self.dfs(move, n, next_board, next_active_mask, ans)
48
49 def is_valid(self, board: List[tuple]) -> bool:
50 # Check boundary and uniquness of the positions
51 positions = set()
52 for x, y in board:
53 if x < 0 or x >= 8 or y < 0 or y >= 8:
54 return False
55 if (x, y) in positions:
56 return False
57 positions.add((x, y))
58 return True
59
60 def hash(self, board: List[tuple]) -> int:
61 hashed = 0
62 for x, y in board:
63 hashed = hashed * 64 + x * 8 + y
64 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
1
2javascript
3class Solution {
4 countCombinations(pieces, positions) {
5 const n = pieces.length;
6 const comb_moves = this.getCombMoves(pieces);
7 const board = positions.map(([x, y]) => [x - 1, y - 1]);
8
9 const ans = new Set();
10 for (const move of comb_moves) {
11 this.dfs(move, n, board, (1 << n) - 1, ans);
12 }
13
14 return ans.size;
15 }
16
17 getCombMoves(pieces) {
18 const pieces_move = pieces.map(piece => this.getMove(piece));
19 return product(...pieces_move);
20 }
21
22 getMove(piece) {
23 const moves = {
24 "rook": [[1, 0], [-1, 0], [0, 1], [0, -1]],
25 "bishop": [[1, 1], [1, -1], [-1, 1], [-1, -1]],
26 "queen": [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]]
27 };
28 return moves[piece];
29 }
30
31 dfs(move, n, board, active_mask, ans) {
32 if (active_mask === 0) {
33 return;
34 }
35
36 ans.add(this.hash(board));
37 for (let next_active_mask = 1; next_active_mask < (1 << n); next_active_mask++) {
38 if (!(active_mask & next_active_mask)) {
39 continue;
40 }
41
42 const next_board = [...board];
43
44 for (let i = 0; i < n; i++) {
45 if ((next_active_mask >> i) & 1) {
46 next_board[i] = [next_board[i][0] + move[i][0], next_board[i][1] + move[i][1]];
47 }
48 }
49
50 if (this.isValid(next_board)) {
51 this.dfs(move, n, next_board, next_active_mask, ans);
52 }
53 }
54 }
55
56 isValid(board) {
57 const positions = new Set();
58 for (const [x, y] of board) {
59 if (x < 0 || x >= 8 || y < 0 || y >= 8) {
60 return false;
61 }
62
63 const key = x * 8 + y;
64 if (positions.has(key)) {
65 return false;
66 }
67
68 positions.add(key);
69 }
70
71 return true;
72 }
73
74 hash(board) {
75 let hashed = 0;
76 for (const [x, y] of board) {
77 hashed = hashed * 64 + x * 8 + y;
78 }
79 return hashed;
80 }
81}
82
83function product(...arrays) {
84 const f = (a, b) => [].concat(...a.map(x => b.map(y => [].concat(x, y))));
85 return arrays.reduce(f, [[]]);
86}
Solution in Java
1
2java
3import java.util.*;
4
5class Solution {
6 public int countCombinations(List<String> pieces, List<List<Integer>> positions) {
7 int n = pieces.size();
8 List<List<int[]>> comb_moves = getCombMoves(pieces);
9 List<int[]> board = new ArrayList<>();
10 for (List<Integer> pos : positions) {
11 board.add(new int[] {pos.get(0) - 1, pos.get(1) - 1});
12 }
13
14 Set<Integer> ans = new HashSet<>();
15 for (List<int[]> move : comb_moves) {
16 dfs(move, n, board, (1 << n) - 1, ans);
17 }
18
19 return ans.size();
20 }
21
22 private List<List<int[]>> getCombMoves(List<String> pieces) {
23 List<List<int[]>> pieces_move = new ArrayList<>();
24 for (String piece : pieces) {
25 pieces_move.add(getMove(piece));
26 }
27
28 return cartesianProduct(pieces_move);
29 }
30
31 private List<int[]> getMove(String piece) {
32 Map<String, int[][]> moves = new HashMap<>();
33 moves.put("rook", new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}});
34 moves.put("bishop", new int[][] {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}});
35 moves.put("queen", new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}});
36
37 return Arrays.asList(moves.get(piece));
38 }
39
40 private void dfs(List<int[]> move, int n, List<int[]> board, int active_mask, Set<Integer> ans) {
41 if (active_mask == 0) {
42 return;
43 }
44
45 ans.add(hash(board));
46 for (int i = 1; i < (1 << n); i++) {
47 int next_active_mask = i;
48 if ((active_mask & next_active_mask) == 0) {
49 continue;
50 }
51
52 List<int[]> next_board = new ArrayList<>();
53 for (int[] pos : board) {
54 next_board.add(pos.clone());
55 }
56
57 for (int j = 0; j < n; j++) {
58 if ((next_active_mask >> j) % 2 == 1) {
59 next_board.set(j, new int[] {next_board.get(j)[0] + move.get(j)[0], next_board.get(j)[1] + move.get(j)[1]});
60 }
61 }
62
63 if (isValid(next_board)) {
64 dfs(move, n, next_board, next_active_mask, ans);
65 }
66 }
67 }
68
69 private boolean isValid(List<int[]> board) {
70 Set<Integer> positions = new HashSet<>();
71 for (int[] pos : board) {
72 int x = pos[0], y = pos[1];
73 if (x < 0 || x >= 8 || y < 0 || y >= 8) {
74 return false;
75 }
76
77 int key = x * 8 + y;
78 if (positions.contains(key)) {
79 return false;
80 }
81
82 positions.add(key);
83 }
84
85 return true;
86 }
87
88 private int hash(List<int[]> board) {
89 int hashed = 0;
90 for (int[] pos : board) {
91 int x = pos[0], y = pos[1];
92 hashed = hashed * 64 + x * 8 + y;
93 }
94 return hashed;
95 }
96
97 private static <T> List<List<T>> cartesianProduct(List<List<T>> lists) {
98 List<List<T>> result = new ArrayList<>();
99 if (lists.isEmpty()) {
100 result.add(Collections.emptyList());
101 return result;
102 }
103
104 List<T> firstList = lists.get(0);
105 List<List<T>> subresult = cartesianProduct(lists.subList(1, lists.size()));
106
107 for (T item : firstList) {
108 for (List<T> subitem : subresult) {
109 List<T> newItem = new ArrayList<>();
110 newItem.add(item);
111 newItem.addAll(subitem);
112 result.add(newItem);
113 }
114 }
115
116 return result;
117 }
118}
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).
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Solution Implementation
How does merge sort divide the problem into subproblems?
What is the worst case running time for finding an element in a binary search tree(not necessarily balanced) of size n?
Recommended Readings
Top Patterns to Conquer the Technical Coding Interview Should the written word bore you fear not A delightful video alternative awaits iframe width 560 height 315 src https www youtube com embed LW8Io6IPYHw title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture
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
Got a question? Ask the Teaching Assistant anything you don't understand.
Still not clear? Ask in the Forum, Discord or Submit the part you don't understand to our editors.