【前端er每日算法】回溯算法-n皇后-求数独

80 阅读2分钟

题目一 51. N 皇后

对于n皇后问题,想必大家都听过,不管上学的时候有没有学会,肯定是学过。毕业了这么多年,今天竟然是第一次实现,😄,我这个算法渣渣,好了,题目大家直接看链接,看了解题后发现,唉,这题好像没那么难。

思路

  1. 遍历棋盘,每一行循环是用递归模拟的,每一列是代码的循环,在每个位置判断,当前如果放入皇后,是否有效,如果有效,则放入皇后,然后递归到下一行放皇后。
  2. 终止条件,如果当前递归的行等于了棋盘的高度,则说明结果有效,将当前的棋盘布局放入到最后的结果集中。
  3. 对于判断某一位置放置皇后是否有效:当前行没有皇后,当前列没有皇后,当前位置45度斜线上没有皇后,135度斜线上没有皇后,如果都符合的话,说明当前位置有效。
function isValid(row, col, chessboard, n) {
    // 行是否符合
    for (let i = 0; i < n; i++) {
        if (i !== col && chessboard[row][i] === 'Q') {
            return false;
        }
    }
    // 列是否符合
    for (let i = 0; i < n; i++) {
        if (i !== row && chessboard[i][col] === 'Q') {
            return false;
        }
    }
    // 45斜角线是否符合
    for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
        if (chessboard[i][j] === 'Q') {
            return false
        }
    }
    for (let i = row + 1, j = col + 1; i < n && j < n; i++, j++) {
        if (chessboard[i][j] === 'Q') {
            return false
        }
    }
    // 135°是否符合
    for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
        if (chessboard[i][j] === 'Q') {
            return false
        }
    }
    for (let i = row + 1, j = col - 1; i < n && j >= 0; i++, j--) {
        if (chessboard[i][j] === 'Q') {
            return false
        }
    }
    return true;
}

var solveNQueens = function(n) {
    const chessboard = new Array(n).fill('.').map(item => new Array(n).fill('.'));
    const result = [];
    const backtracking = (row) => {
        if (row === n) {
            const res = chessboard.map(item => item.join(''));
            result.push(res.slice());
            return;
        }
        for (let i = 0; i < n; i++) {
            if (isValid(row, i, chessboard, n)) {
                chessboard[row][i] = 'Q';
                backtracking(row + 1);
                chessboard[row][i] = '.';
            }
        }
    }
    backtracking(0);
    return result;
};

题目二 37. 解数独

数独大家都知道,所以这就看见了算法的实际用途了,不会填的时候写个算法,就出来结果了😄,自己想是没想出来,后来看了结果,一开始也没看明白,又自己画了画数独,好像是明白了。这题和n皇后的区别是:

  1. 这个题目只需要求出一个正确的填充结果,
  2. 这个题每个位置可以填充1-9,9个数字,而上面的题就填充Q。

所以解法上还是很不一样的。

image.png

思路

依次遍历每个位置,然后每个位置上依次尝试填充1-9个数字。这个递归有返回值了,返回true或者false,true代表当前位置找到了一个可以存放的值,false说明当前棋盘走不下去了,停止回溯。

  1. 如果当前填充的数字是有效的,则递归继续填充下一个没有存放数字的位置。
  2. 如果当前位置遍历完1-9个数字都不合适,则返回false,说明当前棋盘是无法符合条件的,全部递归一路返回回去。
  3. 如果所有的格子都遍历完了,就是循环走完了,没有在中间跳出,说明是找到了一个符合条件的结果,直接返回。
  4. 这里判断当前位置是否有效的逻辑跟n皇后类似,依次判断行列,9宫格的位置是否有重复元素。
function isValid(row, col, chess, num, n) {
    // 行上是否有相同数字
    for (let i = 0; i < n; i++) {
        if (chess[row][i] === num) {
            return false;
        }
    }
    // 列上是否有相同数字
    for (let i = 0; i < n; i++) {
        if (chess[i][col] === num) {
            return false;
        }
    }
    const startRow = parseInt(row / 3, 10);
    const startCol = parseInt(col / 3, 10);
    for (let i = startRow * 3; i < (startRow + 1) * 3; i++) {
        for (let j = startCol * 3; j < (startCol + 1) * 3; j++)
        if (chess[i][j] === num) {
            return false;
        }
    }
    return true;
}

var solveSudoku = function(board) {
    const len = board.length;
    const backtracking = () => {
        for (let i = 0; i < len; i++) {
            for (let j = 0; j < len; j++) {
                if (board[i][j] !== '.') {
                    continue;
                }
                for (let k = 1; k <= 9; k++) {
                    if (isValid(i, j, board, '' + k, len)) {
                        board[i][j] = '' + k;
                        if (backtracking(board)) {
                            return true;
                        }
                        board[i][j] = '.';
                    }
                }
                return false;
            }
        }
        return true;
    };
    backtracking();
    return board;
};

总结

好了,以上就是两道题,没想到竟然做了hard的题目了,仔细一想,这两道题目似乎也没那么难?为什么不会呢?因为之前不会回溯?这个榆木脑袋真是不容易。继续努力加油吧。

这两天把回溯的几道题又刷了一遍,回溯算法有套路,按照套路写,似乎简单一些,但是稍微变形的时候就会想不到,先脑图总结一波,但不是很全面,第三遍再补充吧。

回溯算法.png