题目一 51. N 皇后
对于n皇后问题,想必大家都听过,不管上学的时候有没有学会,肯定是学过。毕业了这么多年,今天竟然是第一次实现,😄,我这个算法渣渣,好了,题目大家直接看链接,看了解题后发现,唉,这题好像没那么难。
思路
- 遍历棋盘,每一行循环是用递归模拟的,每一列是代码的循环,在每个位置判断,当前如果放入皇后,是否有效,如果有效,则放入皇后,然后递归到下一行放皇后。
- 终止条件,如果当前递归的行等于了棋盘的高度,则说明结果有效,将当前的棋盘布局放入到最后的结果集中。
- 对于判断某一位置放置皇后是否有效:当前行没有皇后,当前列没有皇后,当前位置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-9,9个数字,而上面的题就填充Q。
所以解法上还是很不一样的。
思路
依次遍历每个位置,然后每个位置上依次尝试填充1-9个数字。这个递归有返回值了,返回true或者false,true代表当前位置找到了一个可以存放的值,false说明当前棋盘走不下去了,停止回溯。
- 如果当前填充的数字是有效的,则递归继续填充下一个没有存放数字的位置。
- 如果当前位置遍历完1-9个数字都不合适,则返回false,说明当前棋盘是无法符合条件的,全部递归一路返回回去。
- 如果所有的格子都遍历完了,就是循环走完了,没有在中间跳出,说明是找到了一个符合条件的结果,直接返回。
- 这里判断当前位置是否有效的逻辑跟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的题目了,仔细一想,这两道题目似乎也没那么难?为什么不会呢?因为之前不会回溯?这个榆木脑袋真是不容易。继续努力加油吧。
这两天把回溯的几道题又刷了一遍,回溯算法有套路,按照套路写,似乎简单一些,但是稍微变形的时候就会想不到,先脑图总结一波,但不是很全面,第三遍再补充吧。