递归与回溯
递归 Recursion¶
- 将问题规模变小
- 利用小规模问题的结果
- 结合当前值或情况,得出最终结果
递归代码模版¶
function fn(n) {
// STEP 1. 判断输入 or 状态是否非法?
if (input/state is invalid) {
return;
}
// STEP 2. 判断递归是否应该结束?
if (match condition) {
return some value;
}
// STEP 3. 将问题规模变小
result1 = fn(n1);
result2 = fn(n2);
// ...
// STEP 4. 整合结果
return combine(result1, result2);
}
- 判断当前状况是否非法,若非法,则返回。称为 完整性检查(Sanity Check)
- 判断是否满足递归结束条件
- 将问题规模缩小,递归调用
- 利用小规模问题的答案,结合当前数据,整合成最终答案
汉诺塔¶
function hano(A, B, C, n) {
if (n > 0) {
hano(A, C, B, n - 1);
console.log(A + '->' + C); // move(A, C);
hano(B, A, C, n - 1);
}
}
91. 解码方法¶
var numDecodings = function(s) {
return decode(s, s.length - 1)
};
function decode(s, index) {
if(s[0] === '0' && index === 0) {
return 0;
}
if (index <= 0) {
return 1;
}
let last = s[index];
let last2 = s[index - 1];
let count = 0;
if (last > '0') {
count = decode(s, index - 1);
}
if (last2 === '1' || (last2 === '2' && last <='6')) {
count += decode(s, index - 2);
}
return count;
}
回溯 Backtracking¶
- 区别于暴力搜索:一步步向前试探,对每一步探测的情况评估,再决定是否继续。
- 精华在于:出现非法,可以退一步或者多步,再尝试别的路径。
回溯代码模版¶
function fn(n) {
// STEP 1. 判断输入 or 状态是否非法?
if (input/state is invalid) {
return;
}
// STEP 2. 判断递归是否应该结束?
if (match condition) {
return some value;
}
// 遍历所有可能的情况
for (all possible cases) {
// STEP 3. 尝试下一步的可能性
solution.push(case);
// 递归
result = fn(m);
// STEP 4. 回溯到上一步
solution.pop(case);
}
}
- 完整性检查(Sanity Check)
- 判断是否满足递归结束条件?如果是,保当前结果并返回
- 遍历所有可能的情况,进行递归
- 递归完成后,立即回溯,即取消前一步的尝试
39. 组合总和¶
搜索回溯思想:
- 以 target = 7 为 根结点 ,创建一个分支的时 做减法;
- 从父亲结点的数值减去边上的数值,得到孩子结点的数值。边的值就是题目中给出的 candidate 数组的每个元素的值;
- 减到 0 或者负数的时候停止,即:结点 0 和负数结点成为叶子结点;
- 所有从根结点到结点 0的路径(只能从上往下,没有回路)就是要找的一个结果。
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function(candidates, target) {
let results = [];
backtracking(candidates, target, 0, [], results);
return results;
};
function backtracking(candidates, target, start, solution, results) {
if (target < 0) {
return;
}
if (target === 0) {
results.push([...solution]);
return;
}
for (let i = start; i < candidates.length; i++) {
solution.push(candidates[i])
backtracking(candidates, target - candidates[i], i, solution, results);
solution.pop();
}
}
52. N皇后 II¶
/**
* @param {number} n
* @return {number}
*/
var totalNQueens = function(n) {
const results = [];
const board = Array.from({ length: n }).map(() => Array.from({ length: n }));
backtracking(0, board, [], results);
return results.length;
};
function backtracking(row, board, solution, results) {
const n = board.length;
if (row === board.length) {
console.log('solution:', [...solution]);
results.push([...solution]);
return;
}
for (let col = 0; col < n; col++) {
solution.push(row + ',' + col);
board[row][col] = 'Q';
if (check(row, col, board)) {
backtracking(row + 1, board, solution, results);
}
board[row][col] = '.';
solution.pop();
}
}
// check 函数还可以优化,从 O(n^2) 到 O(n)
function check(row, col, board) {
const n = board.length;
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (i === row && j === col) {
continue;
}
if (board[i][j] === 'Q' &&
(
i === row || // 同行
col === j || // 同咧
i + j === row + col || // 左斜对角线
i - j === row - col // 右斜对角线
)
) {
return false;
}
}
}
return true;
}