回溯描述。
分类依据:代码随想录 (programmercarl.com)和力扣
可能的参数:
候选集合
问题规模为树的宽度width
递归深度为树的深度depth
当前结点问题规模的起始索引start_index
标记集合中元素是否已选取的数组
结果集合res和results
注意:子集问题,在树的每结点获取结果
// 0. 将回溯问题抽象为树形结构
// 1. 确定回溯函数的参数和返回值
void backtrack(参数)
{
// 2. 确定回溯函数的终止条件和获取结果逻辑
if (终止条件)
{
获取结果;
return;
}
// 3. 确定回溯函数的遍历和处理结果逻辑
for (当前树层,各结点的问题规模)
{
处理结果;
backtrack(参数); // 递归
撤销结果; // 回溯
}
}
77.组合 - 力扣(LeetCode)——中等的题解:
class Solution
{
private:
// 0. 将回溯问题抽象为树形结构
vector<int> res;
vector<vector<int>> results;
// 1. 确定回溯函数的参数和返回值
void backtrack(int width, int depth, int start_index)
{
// 2. 确定回溯函数的终止条件和获取结果逻辑
if (res.size() == depth)
{
results.push_back(res);
return;
}
// 3. 确定回溯函数的遍历和处理结果逻辑
for (int i = start_index; i <= width; ++i)
{
res.push_back(i); // 处理结果
backtrack(width, depth, i + 1); // 递归
res.pop_back(); // 撤销结果 回溯
}
}
public:
vector<vector<int>> combine(int n, int k)
{
backtrack(n, k, 1);
return results;
}
};
时间复杂度:指数级
组合问题:
如: O ( ∁ m n × n ) O(\complement^n_m × n) O(∁mn×n)。从m规模中取n规模,有 ∁ m n \complement^n_m ∁mn种组合;每组合判断并加入结果集的时间需要O(n)
如: O ( 2 n × n ) O(2^n × n) O(2n×n)。从n规模中取或不取,有 2 n 2^n 2n种组合;每组合判断并加入结果集的时间需要O(n)
切割和子集问题:类似组合问题
排列问题:全排列
参见:
回溯算法入门级详解 + 练习(持续更新) - 全排列 - 力扣(LeetCode)
本周小结!(回溯算法系列三) | 代码随想录 (programmercarl.com)
图和棋盘问题:具体问题具体分析
作用:降低时间复杂度
描述:
形式:
横向剪枝:缩小当前树层各结点的问题规模:修改for()循环
纵向剪枝:增加终止条件
模板示例:216.组合总和 III - 力扣(LeetCode)——中等的题解:
class Solution
{
private:
// 0. 将回溯问题抽象为树形结构
vector<int> res;
vector<vector<int>> results;
// 1. 确定回溯函数的参数和返回值
// width:[1,9]
void backtrack(int depth, int target_sum, int start_index, int sum)
{
// 2. 确定回溯函数的终止条件和获取结果逻辑
if (sum > target_sum) // 纵向剪枝
{
return;
}
if (res.size() == depth)
{
if (sum == target_sum)
{
results.push_back(res);
return;
}
return;
}
// 3. 确定回溯函数的遍历和处理结果逻辑
for (int i = start_index; i <= 9 - (depth - res.size()) + 1; ++i) // 横向剪枝
{
sum += i;
res.push_back(i); // 处理结果
backtrack(depth, target_sum, i + 1, sum); // 递归
sum -= i;
res.pop_back(); // 撤销结果 回溯
}
}
public:
vector<vector<int>> combinationSum3(int k, int n)
{
backtrack(k, n, 1, 0);
return results;
}
};
如:77.组合 - 力扣(LeetCode)——中等
如:39.组合总和 - 力扣(LeetCode)——中等
如:77.组合 - 力扣(LeetCode)——中等
如:17.电话号码的字母组合 - 力扣(LeetCode)——中等
如:39.组合总和 - 力扣(LeetCode)——中等
排序后才可以,通过相邻元素判断是否重复选取
参见:代码随想录 (programmercarl.com)
如:40.组合总和 II - 力扣(LeetCode)——中等
如:47.全排列 II - 力扣(LeetCode)——中等
如:47.全排列 II - 力扣(LeetCode)——中等
排列问题可以在树层去重,也可以在树枝去重;但在树层去重效率更高。参见:代码随想录 (programmercarl.com)
去重:即标记集合中元素是否已选取
参见:代码随想录 (programmercarl.com)
组合、切割和排列问题,树的叶子结点为结果(遍历符合条件的树枝)
子集问题,树的每个结点为结果(遍历树);所以可以不需要确定回溯函数的终止条件,不存在剪枝的优化方法
组合问题、切割和子集问题,集合是无序的,for()循环从当前结点问题规模的起始索引start_index开始;可能需要去重
排列问题,集合是有序的,for()循环从0开始,必需去重
如:37.解数独 II - 力扣(LeetCode)——困难
组合问题:
切割问题:
子集问题:
排列问题:
其他问题:
图问题:
回溯描述。