LeetCode-37-解数独
in LeetCode with 0 comment

LeetCode-37-解数独

in LeetCode with 0 comment

原题地址:解数独

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

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

空白格用 '.' 表示。

250px-Sudoku-by-L2G-20050714.svg 一个数独。

250px-Sudoku-by-L2G-20050714_solution.svg 答案被标成红色。

Note:

回溯算法

利用回溯算法,将每个格子填上与现有格子不冲突的数字,然后继续处理下一个格子。如果发现这条路走不通,就回溯再尝试下一个数字。

具体的实现代码如下:

/**
 * @param {string[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
const solveSudoku1 = function(board) {
    // 对每一行、每一列及每一个小九宫格分别用一个Set来记录出现过的数字
    let columnSets = new Array(board.length);
    let rowSets = new Array(board.length);
    let boxSets = new Array(board.length);
    for (let i = 0; i < board.length; i ++) {
        columnSets[i] = new Set();
        rowSets[i] = new Set();
        boxSets[i] = new Set();
    }
    let solved = false; // 记录是否已找到解
    let array = []; // 记录未填过的格子,方便处理
    // 获取小九宫的索引
    const getBoxIndex = ([i, j]) => {
        return Math.floor(i/3)*3+Math.floor(j/3);
    };
    // 将填过的数字保存到set中
    const saveNumberToSet = ([i, j]) => {
        rowSets[i].add(board[i][j]);
        columnSets[j].add(board[i][j]);
        boxSets[getBoxIndex([i, j])].add(board[i][j]);
    };
    // 已填过的数字不可行,从set中删除
    const deleteNumberFromSet = ([i, j]) => {
        rowSets[i].delete(board[i][j]);
        columnSets[j].delete(board[i][j]);
        boxSets[getBoxIndex([i, j])].delete(board[i][j]);
    };
    // 判断数字填在某个位置是否与已填过的冲突
    const isOk = (number, place) => {
        return !(columnSets[place[1]].has(number) ||
            rowSets[place[0]].has(number) ||
            boxSets[getBoxIndex(place)].has(number));
    };
    /**
     * 将数字填到空白格子中
     * @param index 空白格子在未填格子列表中的索引
     * @param number 要填的数字
     */
    const placeNumber = (index, number) => {
        if (index === array.length) { // 所有的格子已填满,问题已解决
            solved = true;
            return;
        }
        if (solved) { // 已解决
            return;
        }
        if (number === 10) { // 已不可行,返回
            return;
        }
        if (isOk(`${number}`, array[index])) {
            // 如果没有冲突就将该数字填到格子中
            board[array[index][0]][array[index][1]] = `${number}`;
            saveNumberToSet(array[index]);
            // 然后处理下一个格子
            placeNumber(index + 1, 1);
            if (solved) { // 问题已解决就返回
                return;
            }
            // 问题未解决,这条路走不通,将已填过数字要删掉
            deleteNumberFromSet(array[index]);
            board[array[index][0]][array[index][1]] = '.';
        }
        placeNumber(index, number + 1);
    };

    for (let i = 0; i < board.length; i ++) {
        for (let j = 0; j < board.length; j ++) {
            if (board[i][j] === '.') {
                // 还没有填的格子保存到数组中
                array.push([i, j])
            } else {
                // 已经填过的记录到对应的set中
                saveNumberToSet([i, j])
            }
        }
    }
    // 从第一个未填过的格子开始处理
    placeNumber(0, 1);
};

测试:

let start = new Date();
const test = solveSudoku1;
let 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"]
];
test(board);
/**
 [
     ["5","3","4","6","7","8","9","1","2"],
     ["6","7","2","1","9","5","3","4","8"],
     ["1","9","8","3","4","2","5","6","7"],
     ["8","5","9","7","6","1","4","2","3"],
     ["4","2","6","8","5","3","7","9","1"],
     ["7","1","3","9","2","4","8","5","6"],
     ["9","6","1","5","3","7","2","8","4"],
     ["2","8","7","4","1","9","6","3","5"],
     ["3","4","5","2","8","6","1","7","9"]
 ]
 */
console.log(board);
board = [
    [".","1","4",".",".","7",".","5","."],
    [".","8",".","6","1",".",".",".","2"],
    [".",".",".","8",".",".",".","9","."],
    ["3",".",".",".","2",".",".",".","7"],
    [".",".","6",".","8","5","1",".","9"],
    [".",".","8",".","7",".","5",".","6"],
    [".","2",".",".",".","1",".","7","."],
    ["8",".","7",".","6",".",".",".","."],
    [".","5",".",".",".",".",".","6","3"]
];
test(board);
/**
 [
     ["9","1","4","2","3","7","6","5","8"],
     ["5","8","3","6","1","9","7","4","2"],
     ["7","6","2","8","5","4","3","9","1"],
     ["3","9","5","1","2","6","4","8","7"],
     ["2","7","6","4","8","5","1","3","9"],
     ["1","4","8","9","7","3","5","2","6"],
     ["6","2","9","3","4","1","8","7","5"],
     ["8","3","7","5","6","2","9","1","4"],
     ["4","5","1","7","9","8","2","6","3"]
 ]
 */
console.log(board);
console.log(new Date().getTime() - start.getTime()); // 34

时间复杂度: 由于格子大小确定,时间也是确定的,$O(1)$
空间复杂度: 空间复杂度为$O(1)$