ホーム LeetCode. 37. Sudoku Solver
記事
キャンセル

LeetCode. 37. Sudoku Solver

image

有効な数独盤面には解が1つだけあります。各行・各列・3 × 3 のボックスに 1 から 9 までの数字がそれぞれ一度だけ現れるよう、空欄を盤面上で埋めます。

問題へのリンク

アプローチ

各行・各列・各ボックスについて、すでに使われている数字を9ビットのマスクで記録します。ビット d は数字 d + 1 を表し、ビットが立っていればその数字は配置できません。したがって、空欄に置ける数字は、全ビットのマスクのうち対応する行・列・ボックスのいずれのマスクにも含まれていないビットです。

各再帰ステップで残りの空欄を調べ、候補数字が最も少ないマスを選びます(最小残余値、MRV)。制約の強いマスを先に選ぶことで、分岐数を早い段階で減らせます。候補がないマスがあれば、その探索経路は失敗です。そうでなければ候補を一つずつ試します。盤面に数字を書き込み、対応する3つのマスクのビットを立ててから再帰します。失敗した経路ではビットを消し、マスを '.' に戻します。成功した場合はすぐに返るため、完成した盤面が入力に残ります。

空欄は最大81個で、最悪の場合の探索時間は指数時間です(各空欄で最大9通り)。MRVにより矛盾を早く検出し、分岐を絞ることで実際の探索を効率化します。補助領域は再帰スタックと固定サイズのマスクを合わせて O(81) で、盤面はその場で更新します。

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class Solution {
    private static final int FULL = (1 << 9) - 1;

    private char[][] board;
    private final int[] rows = new int[9];
    private final int[] columns = new int[9];
    private final int[] boxes = new int[9];

    public void solveSudoku(char[][] board) {
        for (int i = 0; i < 9; i++) {
            rows[i] = 0;
            columns[i] = 0;
            boxes[i] = 0;
        }
        this.board = board;
        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                char cell = board[row][column];
                if (cell != '.') {
                    int bit = 1 << (cell - '1');
                    int box = (row / 3) * 3 + column / 3;
                    rows[row] |= bit;
                    columns[column] |= bit;
                    boxes[box] |= bit;
                }
            }
        }
        solve();
    }

    private boolean solve() {
        int bestRow = -1;
        int bestColumn = -1;
        int bestCandidates = 0;
        int fewestChoices = 10;

        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                if (board[row][column] != '.') {
                    continue;
                }

                int box = (row / 3) * 3 + column / 3;
                int candidates = FULL & ~(rows[row] | columns[column] | boxes[box]);
                int choices = Integer.bitCount(candidates);
                if (choices < fewestChoices) {
                    bestRow = row;
                    bestColumn = column;
                    bestCandidates = candidates;
                    fewestChoices = choices;
                    if (choices <= 1) {
                        break;
                    }
                }
            }
            if (fewestChoices <= 1) {
                break;
            }
        }

        if (bestRow == -1) {
            return true;
        }
        if (bestCandidates == 0) {
            return false;
        }

        int box = (bestRow / 3) * 3 + bestColumn / 3;
        while (bestCandidates != 0) {
            int bit = bestCandidates & -bestCandidates;
            bestCandidates -= bit;
            int digit = Integer.numberOfTrailingZeros(bit);

            board[bestRow][bestColumn] = (char) ('1' + digit);
            rows[bestRow] |= bit;
            columns[bestColumn] |= bit;
            boxes[box] |= bit;

            if (solve()) {
                return true;
            }

            rows[bestRow] &= ~bit;
            columns[bestColumn] &= ~bit;
            boxes[box] &= ~bit;
            board[bestRow][bestColumn] = '.';
        }
        return false;
    }
}
この記事は著者により CC BY 4.0 ライセンスで公開されています。