Ежедневен бит(д) на C++ #161, Често срещан проблем за интервю: Sudoku Solver.
Днес ще разгледаме често срещан проблем за интервю в C++: решаване на судоку.
Даден е судоку пъзел като std::vector‹std::vector‹char››, където незапълнените интервали са представени като интервал, решете пъзела.
Правила на судоку:
- всеки от деветте реда, колони и кутии 3x3 трябва да съдържа всички цифри 1..9
Преди да продължите да четете решението, препоръчвам ви да опитате да го решите сами. Ето програма за изследване на компилатор, свързана с няколко тестови случая: https://compiler-explorer.com/z/MnG9EG1jf.
Решение
Въпреки че е възможно да се приложи решаване на судоку, което не предполага, това е проект за много уикенди (говорейки от опит), а не нещо, което да правите по време на интервю за кодиране. В същото време обаче не искаме да внедрим директен инструмент за решаване на отгатвания, който грубо подрежда пъзела.
Хубаво средно положение е прилагането на ограничението Sudoku; всяко число може да се появи само веднъж във всеки ред, колона и поле. Това означава, че няма смисъл да гадаете число, което вече присъства в съответния ред, колона или кутия. Освен това, ако стигнем до ситуация, в която нямаме опции за отгатване, знаем, че този конкретен път за отгатване е невалиден.
Не е нужно да проверяваме отново кои числа вече присъстват в ред/колона/кутия. Вместо това можем да следим използваните числа за всеки ред/колона/кутия, докато изследваме пъзела, като добавяме числата, както предполагаме, и ги премахваме, когато се връщаме от лошо решение.
/* 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); }