- 回溯算法是一种暴力穷举算法。
- 穷举的过程就是遍历一棵多叉树的过程。
- 回溯算法 的代码框架和 多叉树遍历 的代码框架类似:
// 回溯算法框架
List<Value> result;
void backtrack(路径,选择列表){
if(满足结束条件){
result.add(路径)
return;
}
for(选择:选择列表){
做选择;
backtrack(路径,选择列表);
撤销选择;
}
}
// 多叉树遍历框架
void traverse(TreeNode root){
if(root == null){
return;
}
for(TreeNode child : root.children){
traverse(child);
}
}
练习
全排列
/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function(nums) {
let res = [],ans = [];
backTrack(nums,res,ans);
return res;
};
function backTrack (nums, result,) {
if (track.length === nums.length) {
// 更改引用类型的指针。目的;防止回溯行为影响到当前数组状态。
result.push([...track])
}
for (let i = 0; i < nums.length; i++) {
if (track.includes(nums[i])) {
continue
}
track.push(nums[i])
backTrack(nums, result, track)
track.pop()
}
}
N皇后
java版本:
class Solution {
// 符合条件的单个结果(路径)
private List<String> path = new LinkedList<>();
// 符合条件的结果集合
private List<List<String>> ans = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
// 新建字符棋盘,相比操作字符串,更方便
char[][] board = new char[n][n];
// 初始化棋盘
for (int i=0; i<board.length; ++i) {
Arrays.fill(board[i], '.');
}
// 从 row=0 第一行开始遍历
backtracking(n, 0, board);
return ans;
}
private void backtracking(int n, int row, char[][] board) {
// 结束条件:所有行都遍历完成,注意是n不是n-1
if (row == n) {
path = array2List(board);
ans.add(path);
return;
}
// 遍历选择列表,这里的一个选择就是 row+col
for (int col=0; col<n; ++col) {
if (!isValid(n, row, col, board)) {
// 排除不合法的选择(皇后存在冲突)
continue;
}
// 做选择
board[row][col] = 'Q';
// 递归调用,进入下一行的决策
backtracking(n, row+1, board);
// 撤销选择
board[row][col] = '.';
}
}
/**
* 将符合条件的某个棋盘数组转换成字符串列表
*/
private List<String> array2List(char[][] board) {
List<String> ans = new ArrayList<>();
for (int i=0; i<board.length; ++i) {
ans.add(String.valueOf(board[i]));
}
return ans;
}
/**
* 判断是否可以在当前棋盘 board[row][col] 这个位置放置皇后Q
* n是棋盘的大小,避免重复计算,所以作为参数传入
*/
private boolean isValid(int n, int row, int col, char[][] board) {
// 检查board[row][col]这个位置所在这一列正上方中,看是否已经存在 Q,存在说明列存在冲突,不能再放置皇后Q
for (int i=0; i<row; ++i) {
if (board[i][col] == 'Q') {
return false;
}
}
// 检查board[row][col]这个位置左上方对角线是否已经存在皇后,左下方不用检查,因为backtracking函数是一行一行遍历的,下方的还没遍历到呢
for (int i=row-1,j=col-1; i>=0 && j>=0; --i,--j) {
if (board[i][j] == 'Q') {
return false;
}
}
// 检查board[row][col]这个位置右上方对角线是否已经存在皇后,右下方不用检查,因为 backtracking函数是一行一行遍历的,下方的还没遍历到呢
for (int i=row-1,j=col+1; i>=0 && j<n; --i,++j) {
if (board[i][j] == 'Q') {
return false;
}
}
return true;
}
}
js版本:
var solveNQueens = function (n) {
const board = new Array(n).fill(0).map(() => new Array(n).fill('.'));
const result = [];
backTracking(n, 0, board, result);
return result;
};
var judge = function (i, j, board,) {
// 检查列
for (let x = 0; x < i; x++) {
if (board[x][j] === 'Q') {
return false;
}
}
// 对角线
for (let x = i - 1, y = j - 1; x >= 0 && y >= 0; x--, y--) {
if (board[x][y] === 'Q') {
return false;
}
}
// 反对角线
for (let x = i - 1, y = j + 1; x >= 0 && y < n; x--, y++) {
if (board[x][y] === 'Q') {
return false;
}
}
return true;
}
var backTracking = function (n, row, board,) {
if (row == n) {
result.push(board.map(item => item.join('')));
return;
}
for (let col = 0; col < n; col++) {
if (judge(row, col, board,n)) { // 验证合法就可以放
board[row][col] = 'Q'; // 放置皇后
backTracking(n, row + 1, board, result);
board[row][col] = '.'; // 回溯,撤销皇后
}
}
}