Skip to content

Commit 09d0306

Browse files
committed
Implement n-queens
name: n-queens url: https://leetcode.com/problems/n-queens difficulty: hard time: 12ms time-rank: 43.51% time-complexity: O(N!) space: 11.3 MB space-rank: 20.32% space-complexity: O(N) Fixes #262 Signed-off-by: Christopher Friedt <[email protected]>
1 parent 268b978 commit 09d0306

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

n-queens-test.cpp

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright (c) 2021, Christopher Friedt
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#include <gtest/gtest.h>
8+
9+
#include "n-queens.cpp"
10+
11+
using namespace std;
12+
13+
TEST(NQueens, diag_up) {
14+
EXPECT_EQ(0, Solution::diag_up(0, 0));
15+
EXPECT_EQ(1, Solution::diag_up(1, 0));
16+
EXPECT_EQ(1, Solution::diag_up(0, 1));
17+
EXPECT_EQ(2, Solution::diag_up(2, 0));
18+
EXPECT_EQ(2, Solution::diag_up(1, 1));
19+
EXPECT_EQ(2, Solution::diag_up(0, 2));
20+
EXPECT_EQ(4, Solution::diag_up(2, 2));
21+
}
22+
23+
TEST(NQueens, diag_down) {
24+
EXPECT_EQ(2, Solution::diag_down(3, 0, 0));
25+
EXPECT_EQ(1, Solution::diag_down(3, 2, 1));
26+
EXPECT_EQ(4, Solution::diag_down(3, 0, 2));
27+
EXPECT_EQ(2, Solution::diag_down(3, 2, 2));
28+
}
29+
30+
TEST(NQueens, 1) {
31+
int n = 1;
32+
vector<vector<string>> expected = {{"Q"}};
33+
EXPECT_EQ(expected, Solution().solveNQueens(n));
34+
}
35+
36+
TEST(NQueens, 2) {
37+
int n = 2;
38+
EXPECT_EQ(0, Solution().solveNQueens(n).size());
39+
}
40+
41+
TEST(NQueens, 3) {
42+
int n = 3;
43+
EXPECT_EQ(0, Solution().solveNQueens(n).size());
44+
}
45+
46+
TEST(NQueens, 4) {
47+
int n = 4;
48+
vector<vector<string>> expected = {{".Q..", "...Q", "Q...", "..Q."},
49+
{"..Q.", "Q...", "...Q", ".Q.."}};
50+
EXPECT_EQ(expected, Solution().solveNQueens(n));
51+
}
52+
53+
TEST(NQueens, 5) {
54+
int n = 5;
55+
EXPECT_EQ(10, Solution().solveNQueens(n).size());
56+
}
57+
58+
TEST(NQueens, 6) {
59+
int n = 6;
60+
EXPECT_EQ(4, Solution().solveNQueens(n).size());
61+
}
62+
63+
TEST(NQueens, 7) {
64+
int n = 7;
65+
EXPECT_EQ(40, Solution().solveNQueens(n).size());
66+
}
67+
68+
TEST(NQueens, 8) {
69+
int n = 8;
70+
EXPECT_EQ(92, Solution().solveNQueens(n).size());
71+
}
72+
73+
TEST(NQueens, 9) {
74+
int n = 9;
75+
EXPECT_EQ(352, Solution().solveNQueens(n).size());
76+
}

n-queens.cpp

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright (c) 2021, Christopher Friedt
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#include <unordered_set>
8+
#include <vector>
9+
10+
#ifndef BIT
11+
#define BIT(n) (1ULL << (n))
12+
#endif
13+
14+
using namespace std;
15+
16+
// name: n-queens
17+
// url: https://leetcode.com/problems/n-queens
18+
// difficulty: 3
19+
20+
/*
21+
Observations:
22+
- at most 1 queen may be on each row
23+
- at most 1 queen may be on each column
24+
- at most 1 queen may be on a given up diagonal
25+
- at most 1 queen may be on a given down diagonal
26+
- with some examples, we see that
27+
- Q(1) = 1, Q(2) = 0, Q(3) = 0, Q(4) = 2
28+
- Additionally, we see that the number of diagonals = 2n - 1
29+
- However, the naive solution is
30+
C(N,1) * C(N-1,1) * C(N-2,1) .. C(1,1) => O(N!)
31+
with N = 9, there are 362880 unique paths to choose and an internet search
32+
reveals that there are 352 unique solutions.
33+
- for N = 9 we can represent rows, cols, up diagonals and down diagonals each
34+
with a uint32_t the N = 4 solution shows that the result of the first solution
35+
can be reflected vertically and horizontally to come up with valid solutions.
36+
The solutions might not always be unique but they are valid.
37+
- on the same topic, rotations of one solution will also result in other valid
38+
solutions
39+
- reducing the amount of time it takes to determine a path is incorrect could
40+
very well be a good strategy to improving times. so it might be worth it to find
41+
a good way to encode partial paths.
42+
- If we follow the row-major convention, then we can encode a path by specifying
43+
only the column that a queen occupies on each row.
44+
- With that, it becomes obvious that we a search is over when it is of length N
45+
- However, we can also determine much earlier if a particular placement is legal
46+
by examining the state at each subsequent iteration.
47+
- Using row-major / column encoding should allow us to also specify partial
48+
paths. So if we have exhaused all possible paths after a path of length = 2, we
49+
can add that path, as well as all of its mirrors and rotations, to a list of bad
50+
paths.
51+
- For the N = 9 case, each column can be 1 byte. If there are 362880 * 9 =
52+
3265920 bytes to encode every possible path, so that's not even that bad.
53+
- I might just use a string to encode the path, because at least it's hashable
54+
by default.
55+
*/
56+
57+
class Solution {
58+
public:
59+
static inline unsigned popcount(unsigned n) { return __builtin_popcount(n); }
60+
61+
static inline unsigned diag_up(unsigned r, unsigned c) { return r + c; }
62+
63+
static inline unsigned diag_down(unsigned N, unsigned r, unsigned c) {
64+
return N - r - 1 + c;
65+
}
66+
67+
void Q(uint8_t n, unordered_set<string> &paths, string path,
68+
uint16_t used_cols, uint16_t used_diag_up, uint16_t used_diag_down) {
69+
70+
static const array<uint16_t, 10> nsoln = {0, 1, 0, 0, 2,
71+
10, 4, 40, 92, 352};
72+
73+
uint8_t row = path.size() / n;
74+
75+
if (row == n) {
76+
/* TODO: insert reflections, rotations.. */
77+
paths.insert(path);
78+
return;
79+
}
80+
81+
for (int col = n - 1; col >= 0; --col) {
82+
if (BIT(col) & used_cols) {
83+
continue;
84+
}
85+
86+
auto upd = diag_up(row, col);
87+
if (BIT(upd) & used_diag_up) {
88+
continue;
89+
}
90+
91+
auto downd = diag_down(n, row, col);
92+
if (BIT(downd) & used_diag_down) {
93+
continue;
94+
}
95+
96+
string row_str(n, '.');
97+
row_str[col] = 'Q';
98+
99+
Q(n, paths, path + row_str, used_cols | BIT(col), used_diag_up | BIT(upd),
100+
used_diag_down | BIT(downd));
101+
102+
if (nsoln[n] == paths.size()) {
103+
break;
104+
}
105+
}
106+
}
107+
108+
vector<vector<string>> solveNQueens(int n) {
109+
unordered_set<string> paths;
110+
vector<vector<string>> output;
111+
112+
Q(n, paths, "", 0, 0, 0);
113+
114+
for (auto &p : paths) {
115+
vector<string> soln;
116+
for (size_t i = 0, N = p.size(); i < N; i += n) {
117+
soln.push_back(p.substr(i, n));
118+
}
119+
output.push_back(soln);
120+
}
121+
122+
return output;
123+
}
124+
};

0 commit comments

Comments
 (0)