目录
理论回顾
什么是回溯法
回溯是一种类似枚举的搜索方法,回溯和递归相辅相成。
回溯法的效率
回溯法本质是穷举,也就是检索所有可能最后才找出结果,因此效率并不高。一般为了提高效率都会进行剪枝操作。
回溯法解决的问题
回溯法,一般可以解决如下几种问题:
-
组合问题:N个数里面按一定规则找出k个数的集合
-
切割问题:一个字符串按一定规则有几种切割方式
-
子集问题:一个N个数的集合里有多少符合条件的子集
-
排列问题:N个数按一定规则全排列,有几种排列方式
-
棋盘问题:N皇后,解数独等等
如何理解回溯
回溯解决的问题可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
组合
题干
题目:给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。
思路和代码
递归法
将问题拆解为先选定组合中的一个数,先记录包含这个数且满足条件的组合,再递归从剩余的 n-1 个数中找组合。
-
递归参数和返回值:递归参数是集合大小 n,组合大小 k,组合起始位置 startIndex;在递归过程中不断更新组合和结果集。
-
递归结束的条件:当组合数组已经达到题目要求的组合大小,说明一个组合已经被找到,将该组合插入结果集后返回。
-
递归顺序:先确定组合的起始元素 startIndex,再递归从剩余的 n - startIndex 个元素里继续填充组合。
class Solution {
public:
vector<int> composition; // 记录组合
vector<vector<int>> result; // 记录所有组合结果
void backTracking(int n, int k, int startIndex){
if (composition.size() == k){
// 当组合大小已满足 k,说明已经找到一个组合,插入结果集
result.push_back(composition);
// 返回到上一层
return;
}
for (int i = startIndex; i <= n; ++i) {
composition.push_back(i); // 填充组合的一个位置,接下来要填充下一个位置
backTracking(n,k,i+1); // 找起点为 j+1 的组合数
composition.pop_back(); // 回溯的时候要记得弹出之前插入的数
}
}
vector<vector<int>> combine(int n, int k) {
backTracking(n,k,1); // 起始从 1 开始
return result;
}
};
递归优化:剪枝
当集合中剩余的元素已经没法凑够组合大小时,就不必再进入循环找组合中了。也就是说,只有当 组合中已有的元素个数 + 集合中剩余的元素个数 ≥ 组合大小 时,即当 composition.size() + (n-i+1) >= k 时才需要进入循环。
class Solution {
public:
vector<int> composition; // 记录组合
vector<vector<int>> result; // 记录所有组合结果
void backTracking(int n, int k, int startIndex){
if (composition.size() == k){
// 当组合大小已满足 k,说明已经找到一个组合,插入结果集
result.push_back(composition);
// 返回到上一层
return;
}
for (int i = startIndex; composition.size()+(n-i+1) >= k; ++i) { // for 循环条件中进行剪枝
// 修改了进入循环的条件!!
composition.push_back(i); // 填充组合的一个位置,接下来要填充下一个位置
backTracking(n,k,i+1); // 找起点为 j+1 的组合数
composition.pop_back(); // 回溯的时候要记得弹出之前插入的数
}
}
vector<vector<int>> combine(int n, int k) {
backTracking(n,k,1); // 起始从 1 开始
return result;
}
};
组合总和Ⅲ
题干
题目:找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
-
只使用数字 1 到 9
-
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
思路和代码
从 1~9 九个数字中选 k 个数组合,若组合之和满足 n ,则插入结果集。
递归法
在上一题的基础上,找出每一个组合,如果组合之和为 n,才插入结果集,否则直接返回。
class Solution {
public:
int sum = 0; // 记录组合数之和
vector<int> composition; // 记录组合
vector<vector<int>> result; // 记录所有组合的结果
void backTracking(int k, int n, int startIndex){
if (composition.size() == k){
if (sum == n){ // 当组合之和 sum 等于目标和 n 时,才插入结果集!
result.push_back(composition);
}
return;
}
for (int i = startIndex; i <= 9; ++i) {
composition.push_back(i);
sum += i;
backTracking(k,n,i+1);
composition.pop_back();
sum -= i;
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backTracking(k,n,1);
return result;
}
};
递归优化
原来的集合中包含 1~9 九个数字,且是升序的,而我们是从前往后、从小到大寻找组合,当组合之和 sum 已经大于目标和 n 时,说明后续的组合只会更大,不需要再进入循环中了。
class Solution {
public:
int sum = 0; // 记录组合数之和
vector<int> composition;
vector<vector<int>> result;
void backTracking(int k, int n, int startIndex){
if (composition.size() == k){
if (sum == n){
result.push_back(composition);
}
return;
}
for (int i = startIndex; composition.size()+(9-i+1) >= k && sum+i <= n; ++i) { // 剪枝
// 同理,第一个剪枝操作是当剩余的元素已经凑不够组合大小时,停止循环
// 第二个剪枝操作是若 组合之和 已经大于 n 了,就没必要进入循环了
composition.push_back(i);
sum += i;
backTracking(k,n,i+1);
composition.pop_back();
sum -= i;
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backTracking(k,n,1);
return result;
}
};
电话号码的字母组合
题干
题目:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
说明:
-
0 <= digits.length <= 4
-
digits[i] 是范围 ['2', '9'] 的一个数字。也就是说不会输入除了 2 ~ 9 以外的字符
思路和代码
首先要知道对应的数字都能映射哪些字母,那么就建立一个 map,键值对为 {数字,字符串(能映射的所有字母)}。这道题本质上也是找组合,只不过找的是不同字符的组合,且在找组合之前得先映射。
递归法
递归法的思路是将问题分解为:每次先确认传入的字符串中第一个数字对应的字符,再递归去确认字符串之后的数字对应的字符。
class Solution {
public:
unordered_map<int,string> numsToChar{
{2,"abc"},{3,"def"},{4,"ghi"},{5,"jkl"},
{6,"mno"},{7,"pqrs"},{8,"tuv"},{9,"wxyz"}
};
string composition; // 存储组合
vector<string> result; // 记录所有组合的结果
void backTracking(string digits){
if (digits.size() == 0){
// 当传入的字符串为空,说明已经找到一个组合,插入结果集
result.push_back(composition);
return;
}
int num = digits[0]-'0'; // 每次都要确认第一个数字
string s = numsToChar[num]; // 将数字映射为字符串
for (char c : s) {
composition.push_back(c); // 已经插入一个字符
string newDigits;
// 调整 digits,而不是改 startIndex
if (digits.size() > 1) newDigits = digits.substr(1); // 要从剩余的字符串中寻找元素插入组合
else newDigits = "";
backTracking(newDigits); // 继续递归寻找元素
composition.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if (digits.empty()) return {};
backTracking(digits);
return result;
}
};
标签:21,组合,int,08,随想录,递归,result,回溯,composition
From: https://blog.csdn.net/chan_lay/article/details/141438557