Day22-回溯,leetcode77,216,17
回溯
-
回溯,通过试探性搜索来解决问题,采用试错的思想,逐步构建解决方案,当发现当前路径不能得到有效解时,就会退到上一步,尝试其他可能性。
-
回溯核心:深度优先搜索 + 剪枝
-
回溯到本质是穷举,穷举所有可能,然后选出我们想要的答案。
-
回溯工作流程:
- 逐步构建候选解
- 验证当前部分解是否满足条件
- 如果满足则继续向下探索
- 如果不满足则回溯(撤销最近到选择)
- 重复上述过程直到找到所有解或遍历完所有可能性
-
系统性:按一定顺序枚举所有可能性
-
跳跃性:当发现不满足条件时,跳过该分支的后续搜索
-
递归实现:通常用递归实现深度优先搜索
-
解决问题
- 组合问题,N个数里面按一定规则找出k个数的集合
- 切割问题,一个字符串按一定规则有几种切割方式
- 子集问题,一个N个数的集合里有多少符合条件的子集
- 排列问题,N个数按一定规则全排列,有几种排列方式
- 棋盘问题,N皇后,解数独等等
- 图着色问题
-
回溯三部曲
- 回溯函数模版返回值和参数
- 回溯函数终止条件
- 回溯搜索的遍历过程
// 回溯法模版
function backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
题目
- 组合
-
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
-
你可以按 任何顺序 返回答案。
-
思路
// 未剪枝
var combine = function (n, k) {
// 回溯法
// result,存放符合条件结果的集合
// path,用来存放符合条件结果
let result = [],
path = [];
// startIndex来记录下一层递归,搜索的起始位置
let backtracking = (_n, _k, startIndex) => {
// 终止条件
if (path.length === _k) {
result.push(path.slice());
return;
}
// 循环本层集合元素
for (let i = startIndex; i <= _n; i++) {
path.push(i);
// 递归
backtracking(_n, _k, i + 1);
// 回溯操作
path.pop();
}
};
backtracking(n, k, 1);
return result;
};
var combine = function (n, k) {
// 回溯法
let result = [],
path = [];
let backtracking = (_n, _k, startIndex) => {
// 终止条件
if (path.length === _k) {
result.push(path.slice());
return;
}
// 循环本层集合元素
for (let i = startIndex; i <= _n - (_k - path.length) + 1; i++) {
path.push(i);
// 递归
backtracking(_n, _k, i + 1);
// 回溯操作
path.pop();
}
};
backtracking(n, k, 1);
return result;
};
- 组合总和 III
-
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
-
只使用数字1到9
-
每个数字 最多使用一次
-
返回所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
-
思路
-
回溯三部曲
-
(1)确定递归函数参数,backtracking(n, k, sum, startIndex)
-
(2)确定终止条件path.size === k
-
(3)单层搜索过程
// 未剪枝
var combinationSum3 = function (k, n) {
// 回溯法
let result = [],
path = [];
const backtracking = (_k, targetSum, sum, startIndex) => {
// 终止条件
if (path.length === _k) {
if (sum === targetSum) {
result.push(path.slice());
}
// 如果总和不相等,就直接返回
return;
}
// 循环当前节点,因为只使用数字1到9,所以最大是9
for (let i = startIndex; i <= 9; i++) {
path.push(i);
sum += i;
// 回调函数
backtracking(_k, targetSum, sum, i + 1);
// 回溯
sum -= i;
path.pop();
}
};
backtracking(k, n, 0, 1);
return result;
};
// 剪枝
var combinationSum3 = function (k, n) {
// 回溯法
let result = [],
path = [];
const backtracking = (_k, targetSum, sum, startIndex) => {
if (sum > targetSum) {
return;
}
// 终止条件
if (path.length === _k) {
if (sum === targetSum) {
result.push(path.slice());
}
// 如果总和不相等,就直接返回
return;
}
// 循环当前节点,因为只使用数字1到9,所以最大是9
for (let i = startIndex; i <= 9 - (_k - path.length) + 1; i++) {
path.push(i);
sum += i;
// 回调函数
backtracking(_k, targetSum, sum, i + 1);
// 回溯
sum -= i;
path.pop();
}
};
backtracking(k, n, 0, 1);
return result;
};
- 电话号码的字母组合
- 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
- 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
// 用回溯法枚举所有数字对应字母的组合,递归构建路径,终止时加入结果。
// digits:输入的数字字符串(只包含2-9)。
var letterCombinations = function(digits) {
const k = digits.length;
// map:数字到字母的映射表(如2对应"abc")。
const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
if(!k) return []; // 如果输入为空,返回空数组。
if(k === 1) return map[digits].split(""); // 如果只有一个数字,直接返回对应的字母数组。
// res:存放所有组合结果的数组。path:当前递归路径(正在拼接的字母组合)。
const res = [], path = [];
// 从第0位数字开始回溯,最终返回所有组合。
backtracking(digits, k, 0);
return res;
function backtracking(n, k, a) {
// 终止条件:当 path 长度等于输入数字长度 k,说明已经拼出一个完整组合,加入结果数组。
if(path.length === k) {
res.push(path.join(""));
return;
}
for(const v of map[n[a]]) {
// 遍历当前数字对应的所有字母,每选一个字母就递归进入下一位数字。
path.push(v);
backtracking(n, k, a + 1);
// 回溯时撤销选择(path.pop()),尝试下一个字母。
path.pop();
}
}
};
参考&感谢各路大神
宝剑锋从磨砺出,梅花香自苦寒来。

浙公网安备 33010602011771号