Ежедневный бит (е) C ++ # 161, Распространенная задача на собеседовании: решение судоку.

Сегодня мы рассмотрим распространенную задачу интервью C++: решение судоку.

Дана головоломка судоку в виде std::vector‹std::vector‹char››, где незаполненные пробелы представлены в виде пробела, решите головоломку.

Правила судоку:

  • каждая из девяти строк, столбцов и полей 3x3 должна содержать все цифры 1..9

Прежде чем вы продолжите читать решение, я рекомендую вам попробовать решить его самостоятельно. Вот проводник компилятора, связанный с парой тестов: https://compiler-explorer.com/z/MnG9EG1jf.

Решение

Хотя можно реализовать решатель судоку, который не угадывает, это проект на многие выходные (судя по опыту), а не то, что нужно делать во время собеседования по программированию. Однако в то же время мы не хотим реализовывать простой алгоритм угадывания, который выполняет головоломку методом грубой силы.

Хорошей золотой серединой является применение ограничения судоку; каждое число может появляться только один раз в каждой строке, столбце и поле. Это означает, что нет смысла угадывать число, уже присутствующее в соответствующей строке, столбце или поле. Кроме того, если мы приходим к ситуации, когда у нас нет вариантов угадать, мы знаем, что этот конкретный путь угадывания недействителен.

Нам не нужно постоянно перепроверять, какие числа уже присутствуют в строке/столбце/поле. Вместо этого мы можем отслеживать используемые числа для каждой строки/столбца/поля по мере изучения головоломки, добавляя числа по мере предположения и удаляя их при откате от плохого решения.

/* Calculate the corresponding box for row/col coordinates:
0 1 2
3 4 5
6 7 8

Any mapping will work, as long as it is consistent.
*/
int get_box(int64_t row, int64_t col) {
    return (row/3)*3+col/3;
}

// Get the next empty space
std::pair<int64_t,int64_t> next(
    const std::vector<std::vector<char>>& puzzle,
    int64_t row, int64_t col) {
    int64_t start = col;
    for (int64_t i = row; i < std::ssize(puzzle); ++i)
        for (int64_t j = std::exchange(start,0); 
             j < std::ssize(puzzle[i]); ++j)
            if (puzzle[i][j] == ' ')
                return {i,j};
    return {-1,-1};
}

bool backtrack(
    std::vector<std::vector<char>>& puzzle,
    std::array<std::bitset<9>,9>& row,
    std::array<std::bitset<9>,9>& col,
    std::array<std::bitset<9>,9>& box,
    int64_t r_curr, int64_t c_curr) {

    // next coordinate to fill
    auto [r_next, c_next] = next(puzzle, r_curr, c_curr);
    // {-1,-1} means there is no unfilled space, 
    // i.e. we have solved the puzzle
    if (r_next == -1 && c_next == -1)
        return true;

    // The candidate numbers for this space cannot
    // repeat in the row, column or box.
    auto candidates = row[r_next];
    candidates |= col[c_next];
    candidates |= box[get_box(r_next,c_next)];

    // Guess a number
    for (int64_t i = 0; i < 9; ++i) {
        // Already in a row, column or box
        if (candidates[i]) continue;

        // Mark it on the puzzle, row, column and box
        puzzle[r_next][c_next] = '1'+i;
        row[r_next][i] = true;
        col[c_next][i] = true;
        box[get_box(r_next,c_next)][i] = true;

        if (backtrack(puzzle,row,col,box,r_next,c_next))
            return true;
        // we get false if this was a guess
        // that didn't lead to a solution

        // Unmark from the box, column, row and puzzle
        box[get_box(r_next,c_next)][i] = false;
        col[c_next][i] = false;
        row[r_next][i] = false;
        puzzle[r_next][c_next] = ' ';
        // And try the next digit
    }
    return false;
}

bool solve(std::vector<std::vector<char>>& puzzle) {
    std::array<std::bitset<9>,9> row;
    std::array<std::bitset<9>,9> col;
    std::array<std::bitset<9>,9> box;

    // Preprocessing step, mark the starting clues
    // in the corresponding rows, columns and boxes
    for (int64_t i = 0; i < 9; ++i)
    for (int64_t j = 0; j < 9; ++j)
    if (puzzle[i][j] != ' ') {
        int64_t digit = puzzle[i][j]-'1';
        row[i][digit] = true;
        col[j][digit] = true;
        box[get_box(i,j)][digit] = true;
    }

    return backtrack(puzzle,row,col,box,0,0);
}

Откройте решение в Compiler Explorer.