バックトラックで 互いに 取り合わない n 個の クイーンの 置き方を 全部集める
n 行 n 列の
海外で定番の問題リスト 4 本には入っていません
問題
チェスのn を
- 1 つの
置き方は、 盤の 各行を 文字列に した n 個の 並びで 表します。 クイーンの ある マスは "Q"、無い マスは "."です - 置き方
どうしの 順は、 1 行目の クイーンの 列が 左に ある方を 先にします。 1 行目が 同じなら 2 行目の 列が 左に ある方を 先に、と下へ 続けて比べます - 置き方が
無ければ、 空の 並び []を返します nは 1 以上 9 以下です
入力と出力の例
例 1
4 行 4 列では、
例 2
6 行 6 列では、
例 3
3 行 3 列では、
面接での聞かれ方
面接では、
- 「n クイーン問題を
解いて、 全部の 置き方を 返してください」 - 書き終えると
「置けるか どうかの 確かめは、 何でしていますか? 毎回盤を 全部見ていませんか?」と 聞かれます - 「置き方の
数だけを 返すなら、 どこを 変えますか?」と、 続けて 聞かれる ことがあります
盤の
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「再帰と木」に、
関わる学び
再帰は、
まず 考える手順
段 1
n × n の
段 2
r 行目に
段 3
置けるか
段 4
置いたcols[r] が r 行目の
解答
Python
Python
def n_queens(n):
out = []
cols = []
used = set()
up = set()
down = set()
def place(r):
if r == n:
rows = board(cols)
out.append(rows)
return
for c in range(n):
u, d = r + c, r - c
if c in used:
continue
if u in up:
continue
if d in down:
continue
used.add(c)
up.add(u)
down.add(d)
cols.append(c)
place(r + 1)
cols.pop()
used.remove(c)
up.remove(u)
down.remove(d)
place(0)
return out
def board(cols):
n = len(cols)
rows = []
for c in cols:
a = ["."] * n
a[c] = "Q"
rows.append("".join(a))
return rowsJavaScript
JavaScript
function nQueens(n) {
const out = [];
const cols = [];
const used = new Set();
const up = new Set();
const down = new Set();
// 0 から n - 1 までの列
const all = [
...Array(n).keys()];
const place = (r) => {
if (r === n) {
out.push(board(cols));
return;
}
for (const c of all) {
if (used.has(c) ||
up.has(r + c) ||
down.has(r - c)) {
continue;
}
used.add(c);
up.add(r + c);
down.add(r - c);
cols.push(c);
place(r + 1);
cols.pop();
used.delete(c);
up.delete(r + c);
down.delete(r - c);
}
};
place(0);
return out;
}
function board(cols) {
const n = cols.length;
return cols.map((c) =>
".".repeat(c) + "Q" +
".".repeat(n - 1 - c));
}Go
Go
import "strings"
func nQueens(n int) [][]string {
out := [][]string{}
cols := []int{}
used := make([]bool, n)
up := make([]bool, 2*n)
down := make([]bool, 2*n)
var place func(r int)
place = func(r int) {
if r == n {
out = append(out,
board(cols))
return
}
for c := 0; c < n; c++ {
// r - c は負になるので
// n を足して番号にする
d := r - c + n
if used[c] || up[r+c] ||
down[d] {
continue
}
used[c] = true
up[r+c] = true
down[d] = true
cols = append(cols, c)
place(r + 1)
cols = cols[:r]
used[c] = false
up[r+c] = false
down[d] = false
}
}
place(0)
return out
}
func board(
cols []int,
) []string {
n := len(cols)
rows := []string{}
for _, c := range cols {
rows = append(rows,
strings.Repeat(".", c)+
"Q"+
strings.Repeat(
".", n-1-c))
}
return rows
}Go は
計算量
- 時間は O(n!) です。
1 行目は n 列、 2 行目は 多くて n - 1 列、と 置ける 所が 減っていきます。 斜めで 切られる 分、 実際は もっと 少なく 済みます。 答えを 盤の 文字列に するのに、 1 つごとに O(n²) かかります - 使う
記憶は、 答えを 除くと O(n) です。 再帰の 深さと、 3 つの 集合と 列の 並びが、 どれも n 前後までです
よく ある間違い
- 斜めを 1 本しか見ない。
右上がり (r + c) と 右下がり (r - c) の 2 本が あります。 片方だけだと、 例 1 で 取り合う 置き方まで 答えに入ります - 戻る
ときに 集合から 外し忘れる。 次の 列を 試すとき、 取り除いたはずの クイーンの 列や 斜めが ふさが ったままに なり、 置き方を 見落とします - 盤の
全部の マスで 置けるかを 確かめる。 1 回で O(n²) かかり、 遅くなります。 行は 1 個ずつと 決めて あるので、 列と 2 本の 斜めだけを 集合で 見れば足ります - Go で r - c を
そのまま 番号にする。 負の 番号に なって 止まります。 n を 足して 0 以上にします
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「同じ
行には 1 個しか 置けないので、 上の 行から 1 行に 1 個ずつ置く 再帰にします。 各行で 列を 左から 試し、 使った 列、 r + c の 斜め、 r - c の 斜めの 3 つの 集合の どれにも 入っていなければ 置いて 次の 行へ 進み、 戻ったら 外します。 全部の 行に 置けたら 盤の 文字列に して 足します。 置ける 所は 行ごとに 減るので、 時間は O(n!) 程度です。」
書き終えたら、
「例 1 の n = 4 で、
1 行目を 左端に 置くと、 2 行目は 3 列目か 4 列目です。 どちらを 選んでも 3 行目か 4 行目で 置ける 所が 無くなり、 戻ります。 1 行目を 2 列目に すると、 2 行目は 4 列目、 3 行目は 1 列目、 4 行目は 3 列目と 置けて、 1 つ目の 置き方です。」