LeetCode 37 Sudoku Solver (Tag:Array Difficulty:Hard)|8月更文挑战

448 阅读3分钟

前言

关于 LeetCode 数组类型题目的相关解法,可见LeetCode 数组类型题目做前必看,分类别解法总结了题目,可以用来单项提高。觉得有帮助的话,记得多多点赞关注哦,感谢!

题目描述

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.' 表示。

示例:
输入
image.png
输出

image.png

链接:leetcode-cn.com/problems/su…

题解

采用 DFS + backtracking(回溯) 的方法.
首先, 构建三个标记数组, rows, cols, boxes, 分别表示 行, 列, 子格子, 其中每个标记数组的大小都为 9×10 并且初始化每个元素为0. 那么 i, j 表示该行(列, 子格子)中的数字 j 是否被使用过, 0代表未使用, 1代表使用过了.比如, rows[0][3] = 1 代表 board 中第0行数字3已经被使用过了. 那么我们就可以利用这三个标记数组来遍历插入数字.
那么如何遍历呢?
根据 board 初始值, 我们可以将 rows, cols, boxes 标记数组初始化, 将已经在棋盘上的数字在各自的标记数组中置1. 然后依次从 1-9 的数字中, 找出符合 rows, cols, boxes 三个标志位均为 0 的数字, 并将其填入 board 中, 标志位置为1. 之后, 问题就变成在已知目前的 board 情况下, 该如何填满 board, 即递归调用, 也就是** dfs**. 当递归调用不满足条件时, 需要将 board 恢复原状, 不影响下个数组填入 board 中, 就是回溯的过程.
递归终止条件为: 递归调用到了行数为9的时候, 这时候说明board 0-8 行 已经填好了, 已经得到符号要求的解了.
代码见下方:

/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solveSudoku = function(board) {
    // 设置标记数组
    rows = Array.from({ length: 9 }, () => new Array(10).fill(0))
    cols = Array.from({ length: 9 }, () => new Array(10).fill(0))
    boxes = Array.from({ length: 9 }, () => new Array(10).fill(0))
    
    // 根据board初始化标记数组
    for (let i = 0; i < 9; ++i) {
        for (let j = 0; j < 9; ++j) {
            const c = board[i][j]
            if (c !== '.') {
                const n = c - '0'
                // 计算子格子的下表
                const indexSubBox = Math.floor(i / 3) * 3 + Math.floor(j / 3) 
                rows[i][n] = 1
                cols[j][n] = 1
                boxes[indexSubBox][n] = 1
            }
        }
    }

    
    const fill = (board, x, y) => {
        // 递归终止条件
        if (x === 9) return true
        
        // 下一个要递归的元素下表
        const next_y = (y + 1) % 9
        const next_x = (next_y === 0) ? x + 1 : x
        
        if (board[x][y] !== '.') return fill(board, next_x, next_y)
        
        // 1-9中找出合适的值填入board
        for (let i = 1; i <= 9; ++i) {
            const indexSubBox = Math.floor(x / 3) * 3 + Math.floor(y / 3)
            // 符合要求的值
            if (!rows[x][i] && !cols[y][i] && !boxes[indexSubBox][i]) {
                // 填入board
                rows[x][i] = 1
                cols[y][i] = 1
                boxes[indexSubBox][i] = 1
                board[x][y] = i.toString()
                // 递归调用(dfs)
                if (fill(board, next_x, next_y)) return true
                // 回溯, 恢复原状, 不影响填入下个符合要求的值
                board[x][y] = '.'
                rows[x][i] = 0
                cols[y][i] = 0
                boxes[indexSubBox][i] = 0
            }
        }
        // 没有符合要求的解,返回false
        return false
    }
    
    fill(board, 0, 0)
};