Разбор задачи: Решение судоку

Предложенный нами код:

 def solveSudoku(board):
    """
    Решает задачу судоку путем заполнения пустых клеток.
    :param board: List[List[str]] - доска судоку 9x9
    :return: None - модифицирует переданный `board` на месте
    """
    def is_valid(num, row, col):
        # Проверяем строку и столбец
        for i in range(9):
            if board[row][i] == num or board[i][col] == num:
                return False
        # Проверяем 3x3 подблок
        start_row, start_col = 3 * (row // 3), 3 * (col // 3)
        for i in range(start_row, start_row + 3):
            for j in range(start_col, start_col + 3):
                if board[i][j] == num:
                    return False
        return True

    def backtrack():
        for row in range(9):
            for col in range(9):
                if board[row][col] == ".":
                    for num in map(str, range(1, 10)):
                        if is_valid(num, row, col):
                            board[row][col] = num
                            if backtrack():
                                return True
                            board[row][col] = "."
                    return False
        return True

    backtrack()

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


1. Входные данные и задача функции

Функция solveSudoku принимает на вход игровую доску судоку в виде 2D-списка board, где каждая клетка представлена символом:

  • Числа от '1' до '9' обозначают заполненные клетки.
  • Символ '.' обозначает пустую клетку.

Функция модифицирует переданный board так, чтобы в нем осталась единственная корректная конфигурация судоку.


2. Проверка валидности числа

def is_valid(num, row, col):
    for i in range(9):
        if board[row][i] == num or board[i][col] == num:
            return False
    start_row, start_col = 3 * (row // 3), 3 * (col // 3)
    for i in range(start_row, start_row + 3):
        for j in range(start_col, start_col + 3):
            if board[i][j] == num:
                return False
    return True

Этот вспомогательный метод проверяет, можно ли разместить число num в клетке с координатами (row, col):

  • Сначала проверяются строки и столбцы. Если число уже присутствует в строке или столбце, возвращается False.
  • Затем проверяется 3x3 подблок, в котором находится клетка. Если число уже присутствует в этом подблоке, возвращается False.
  • Если все проверки пройдены, возвращается True.

Пример: Если мы хотим вставить '5' в клетку (0, 2), проверяются:

  • Все числа в строке 0.
  • Все числа в столбце 2.
  • Все числа в 3x3 блоке, который охватывает (0, 2).

3. Реализация backtracking

def backtrack():
    for row in range(9):
        for col in range(9):
            if board[row][col] == ".":
                for num in map(str, range(1, 10)):
                    if is_valid(num, row, col):
                        board[row][col] = num
                        if backtrack():
                            return True
                        board[row][col] = "."
                return False
    return True

Эта функция реализует метод проб и ошибок. Рассмотрим его работу:

  1. Поиск пустой клетки:
    • Вложенные циклы перебирают все клетки доски.
    • Если текущая клетка пустая ("."), начинается процесс заполнения.
  2. Подстановка возможных значений:
    • Функция перебирает числа от '1' до '9'.
    • Для каждого числа проверяется его валидность с помощью функции is_valid.
  3. Рекурсивный вызов:
    • Если число валидно, оно временно размещается в клетке, и выполняется рекурсивный вызов backtrack для проверки следующей пустой клетки.
    • Если размещение приводит к успешному решению, функция возвращает True.
  4. Откат (backtracking):
    • Если дальнейшее решение невозможно, число удаляется из клетки, и продолжается перебор оставшихся чисел.
    • Если никакое число не подходит, функция возвращает False, сигнализируя об откате на предыдущий шаг.

4. Основная функция

def solveSudoku(board):
    backtrack()

Функция solveSudoku вызывает рекурсивную функцию backtrack, которая находит решение судоку. После выполнения, переданный board содержит корректное заполнение.


Пошаговый пример работы кода

Рассмотрим входную доску:

board = [
    ["5", "3", ".", ".", "7", ".", ".", ".", "."],
    ["6", ".", ".", "1", "9", "5", ".", ".", "."],
    [".", "9", "8", ".", ".", ".", ".", "6", "."],
    ["8", ".", ".", ".", "6", ".", ".", ".", "3"],
    ["4", ".", ".", "8", ".", "3", ".", ".", "1"],
    ["7", ".", ".", ".", "2", ".", ".", ".", "6"],
    [".", "6", ".", ".", ".", ".", "2", "8", "."],
    [".", ".", ".", "4", "1", "9", ".", ".", "5"],
    [".", ".", ".", ".", "8", ".", ".", "7", "9"]
]
  1. Поиск пустой клетки:
    • Первая пустая клетка находится в позиции (0, 2).
  2. Проба чисел:
    • Число '1' проверяется через is_valid и оказывается некорректным (уже есть в строке).
    • Число '4' оказывается корректным и временно размещается в клетке.
  3. Рекурсия:
    • Вызов backtrack продолжается с новой доской.
    • Аналогичные шаги повторяются для следующей пустой клетки.
  4. Откат:
    • Если размещение числа в какой-либо клетке приводит к тупиковой ситуации (нет решений для следующей пустой клетки), выполняется откат: число удаляется, и продолжается перебор оставшихся чисел.
  5. Завершение:
    • После успешного заполнения всех клеток функция возвращает True.

 



Вы должны Войти или Зарегистрироваться чтобы оставлять комментарии