这是回溯算法的第三篇,排列问题和棋盘问题。
前两篇👇
回溯-组合问题
回溯-分割与子集问题
排列问题与组合问题的区别:排列跟顺序有关,当元素相同但元素顺序不同的时是不同的排列。
1. 全排列
注:代码与之前的不同之处,回溯函数里没有index
参数,for
循环里i
每次都是从0开始。因为每次向path
里增加元素只是根据used
数组来判断。
var permute = function(nums) {
const ans = [], path = [];
let used = new Array(nums.length).fill(false);
const backtracing = (used) => {
if(path.length === nums.length) {
ans.push([...path]);
return;
}
for(let i = 0; i < nums.length; i++) {
if(used[i]) continue;
path.push(nums[i]);
used[i] = true;
backtracing(used);
used[i] = false;
path.pop();
}
}
backtracing(used);
return ans;
};
2. 全排列 II
注:比上一题多了去重,依旧是树层去重。要先对数组进行排序。
var permuteUnique = function(nums) {
const ans = [], path = [];
nums.sort();
const backtracing = (used) => {
if(path.length === nums.length) {
ans.push([...path]);
return;
}
for(let i = 0; i < nums.length; i++) {
// 树层去重
if(!used[i - 1] && i > 0 && nums[i] === nums[i - 1]) continue;
// 跳过已经取过的数
if(used[i]) continue;
used[i] = true;
path.push(nums[i]);
backtracing(used);
used[i] = false;
path.pop();
}
}
backtracing([]);
return ans;
};
3. 重新安排行程
var findItinerary = function(tickets) {
let result = ['JFK']
let map = {}
for (const tickt of tickets) {
const [from, to] = tickt
if (!map[from]) {
map[from] = []
}
map[from].push(to)
}
for (const city in map) {
// 对到达城市列表排序
map[city].sort()
}
function backtracing() {
if (result.length === tickets.length + 1) {
return true
}
if (!map[result[result.length - 1]] || !map[result[result.length - 1]].length) {
return false
}
for(let i = 0 ; i < map[result[result.length - 1]].length; i++) {
let city = map[result[result.length - 1]][i]
// 删除已走过航线,防止死循环
map[result[result.length - 1]].splice(i, 1)
result.push(city)
if (backtracing()) {
return true
}
result.pop()
map[result[result.length - 1]].splice(i, 0, city)
}
}
backtracing()
return result
};
4. N 皇后
51. N 皇后-困难
b站视频讲解
棋盘问题的难点:遍历二维数组。
思路:树的每一层是同一行的不同列,深度是棋盘的行数,终止条件是遍历到了最后一行。每次放皇后前都要先检查棋盘的有效性。
var solveNQueens = function(n) {
const ans = [];
// 判断棋盘是否合理
function isValid(row, col, chessBoard, n) {
// 不用检查同行是否存在两个皇后,因为在放皇后时 遍历每一行时已经保证了每行放一个
// 检查同列是否存在两个皇后
for(let i = 0; i < row; i++) {
if(chessBoard[i][col] === 'Q') {
return false;
}
}
// 检查左上方斜线是否有皇后
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 >= 0 && j < n; i--, j++) {
if(chessBoard[i][j] === 'Q') {
return false;
}
}
return true;
}
function transformChessBoard(chessBoard) {
let chess = [];
chessBoard.forEach(row => {
let rowStr = '';
row.forEach(value => {
rowStr += value;
})
chess.push(rowStr);
})
return chess;
}
function backtracing(row, chessBoard) {
if(row === n) {
ans.push(transformChessBoard(chessBoard));
return;
}
for(let col = 0; col < n; col++) {
if(isValid(row, col, chessBoard, n)) {
chessBoard[row][col] = 'Q';
backtracing(row + 1, chessBoard);
chessBoard[row][col] = '.';
}
}
}
let chessBoard = new Array(n).fill([]).map(() => new Array(n).fill('.'));
backtracing(0, chessBoard);
return ans;
};
5. 解数独
本题比上一题(N皇后)多了一个维度,N皇后每行和每列只需放置一个皇后,而本题每个空位都需要放置数字,所以是2维递归,里面是两层for循环,一层遍历行,一层遍历列。
var solveSudoku = function(board) {
function isValid(row, col, val, board) {
// 同行不能有重复数字
for(let i = 0; i < board[0].length; i++) {
if(board[row][i] === val) return false;
}
// 同列不能有重复数字
for(let j = 0; j < board.length; j++) {
if(board[j][col] === val) return false;
}
// 九宫格内不能有重复数字
let startRow = Math.floor(row / 3) * 3;
let startCol = Math.floor(col / 3) * 3;
for(let i = startRow; i < startRow + 3; i++) {
for(let j = startCol; j < startCol + 3; j++) {
if(board[i][j] === val) {
return false;
}
}
}
return true;
}
function backtracing() {
// 遍历行
for(let i = 0; i < board.length; i++) {
// 遍历列
for(let j = 0; j < board[0].length; j++) {
if(board[i][j] !== '.') continue;
for(let val = 1; val <= 9; val++) {
if(isValid(i, j, `${val}`, board)) {
board[i][j] = `${val}`;
if(backtracing()) {
return true;
}
board[i][j] = '.';
}
}
return false;
}
}
return true;
}
backtracing();
return board;
};