目录
前言:
77.组合
题目描述:
输入输出示例:
思路和想法:
216. 组合总和 III
题目描述:
输入输出示例:
思路和想法:
17. 电话号码的字母组合
题目描述:
输入输出描述:
思路和想法:
40. 组合总和 II
题目描述:
输入输出描述:
思路和想法:
前言:
这里对于组合问题,进行了深入的探讨(ACM模式):
- 数组集合里元素不重复,元素只能取一次,组合不许重复
- 引入总和统计
- 引入map哈希
- 引入去重的概念---(数组集合里元素有重复,元素只能取一次,但组合不许重复)
77.组合
题目描述:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
输入输出示例:
示例1:
输入:n = 4,k = 2 输出:[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
示例2:
输入:n = 1,k = 1 输出:[[1]]
提示:
- 1 <= n <= 20
- 1 <= k <= n
思路和想法:
回溯法的问题,都可以抽象为树形结构问题。
回溯法解决的是在集合中递归查找子集,集合的大小构成树的宽度,递归的深度构成树的深度。
这道题目属于组合问题的模板题。
#include <bits/stdc++.h>using namespace std;
/*
* 作者:希希雾里
* 77.组合
* *//*
* 这里我们能够比较清晰的,要使用回溯。
* */
vector<vector<int>> result;
vector<int> path;
//这里对于回溯要传入的参数:元素数量,限定条件以及要开始遍历的元素
//这里能够比较清晰的知道n即为回溯的宽度,k即为回溯的深度。
void backtracing(int n, int k, int startIndex){/*终止条件*/if(path.size() == k){result.push_back(path);return;}/*处理节点 + 回溯撤销*/for (int i = startIndex; i <= n; ++i) {path.push_back(i);backtracing(n, k, i+1);path.pop_back();}
};int main() {int n, k;cin>> n >> k;result.clear();path.clear();backtracing(n, k,1);//结果输出for (int i = 0; i < result.size(); ++i) {for (int j = 0; j < result[i].size(); ++j) {cout << result[i][j];if(j != result[i].size() - 1) cout << " ";}//注意最后输出不需要换行。if(i == result.size() - 1) return 0;cout << endl;}return 0;
}/* 测试样例
4 21 1
*
* */
216. 组合总和 III
题目描述:
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
输入输出示例:
示例1:
输入:k = 3, n = 7 输出:[[1,2,4]]
示例2:
输入:k = 3, n = 9 输出:[[1,2,6], [1,3,5], [2,3,4]]
示例3:
输入: k = 4, n = 1 输出:[]
提示:
- 2 <= k <= 9
- 1 <= n <= 60
思路和想法:
这道题目和上一道题基本一致,在原有基础上,还需要统计path里元素的总和,终止条件发生了改变, path.size() == k && sum == n 。
#include <bits/stdc++.h>using namespace std;
/*
* 作者:希希雾里
* 216.组合总和III
* */
vector<vector<int>> result;
vector<int> path;
int sum = 0;
//这里对于回溯要传入的参数,集合个数,目标总和以及要遍历的元素下标
void backtracing(int k, int n, int startIndex){/*终止条件*/if(path.size() == k && sum == n){result.push_back(path);return;}/*处理节点 + 回溯撤销*/for (int i = startIndex; i <= 9; ++i) {path.push_back(i);sum += i;backtracing(n, k, i + 1);sum -= path.back();path.pop_back();}
};int main() {int n, k;cin>> n >> k;result.clear();path.clear();backtracing(k, n,1);//结果输出for (int i = 0; i < result.size(); ++i) {for (int j = 0; j < result[i].size(); ++j) {cout << result[i][j];if(j != result[i].size() - 1) cout << " ";}//注意最后输出不需要换行。if(i == result.size() - 1) return 0;cout << endl;}return 0;
}
17. 电话号码的字母组合
题目描述:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入输出描述:
示例1:
输入:digits = "23" 输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例2:
输入:digits = "" 输出:[]
示例3:
输入:digits = "2" 输出:["a","b","c"]
提示:
- 0 <= digits.length <= 4
- digits[i] 是范围 ['2', '9'] 的一个数字
思路和想法:
这里首先构建数字和字符之间的映射,后续就是组合问题,采用回溯方法就可以解决了。
#include <bits/stdc++.h>using namespace std;
/*
* 作者:希希雾里
* 17.电话号码的字母组合
* */
vector<string> result;
string s;
//构建map,建立映射
const string map_letter[10] = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
};/*
* 函数: backtracing()
* 输入参数: digits:输入的字符串 index:字符串元素下标
* */
void backtracing(const string & digits, int index){/*终止条件*/if(index == digits.length()){result.push_back(s);return;}int digit = digits[index] - '0';string letters = map_letter[digit];/*处理节点 + 回溯过程*/for (int i = 0; i < letters.length(); ++i) {s.push_back(letters[i]);backtracing(digits, index + 1);s.pop_back();}
};int main() {result.clear();s.clear();//字符串输入string str;getline(cin,str);backtracing(str, 0);//结果输出for (int i = 0; i < result.size(); ++i) {//注意最后输出不需要换行。cout << result[i];if(i == result.size() - 1) return 0;cout << endl;}return 0;
}/* 测试样例
232
*
* */
40. 组合总和 II
题目描述:
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
输入输出描述:
示例1:
输入:candidates = [10,1,2,7,6,1,5], target = 8 输出:[ [1,1,6], [1,2,5], [1,7], [2,6] ]
示例2:
输入:candidates = [2,5,2,1,2], target = 5, 输出:[ [1,2,2], [5] ]
提示:
- 1 <= candidates.length <= 100
- 1 <= candidates[i] <= 50
- 1 <= target <= 30
思路和想法:
这道题目和之前不一样的地方在于,要取的数组里有数值相等的元素并且要求解集不能包含重复的组合。所以这道题目涉及到了去重。
一个组合里可以有重复的元素,即不需要树枝去重(纵向)。不能有重复的组合,即要进行树层去重(横向)。
树层去重和树枝去重,是根据树形结构,提出的去重概念。
那么转换到具体实现上,需要解决两个问题:
- 如何实现去重?这里先对数组进行排序,将重复的元素紧挨在一起,之后遍历时判断与前面的元素是否相等即可。
- 如何分辨树层去重和树枝去重?这里采用一个bool数组used,used[i - 1] == true,说明同一树枝candidates[i - 1]使用过,used[i - 1] == false,说明同一树层candidates[i - 1]使用过。
#include <bits/stdc++.h>using namespace std;
/*
* 作者:希希雾里
* 40. 组合总和 II
* */
vector<vector<int>> result;
vector<int> path;
int sum = 0;/*
* 函数: backtracing()
* 输入参数: candidates:输入的数组 target: 目标数值 index:字符串元素下标 used:标志数组
* */
void backtracing(vector<int>& candidates, int target, int index, vector<bool>& used){if(sum == target){result.push_back(path);return;}for(int i = index; i < candidates.size();++i){if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){continue;}path.push_back(candidates[i]);sum += candidates[i];used[i] = true;backtracing(candidates, target, i + 1, used);used[i] = false;sum -= candidates[i];path.pop_back();}
};int main() {result.clear();path.clear();vector<int> candidates;int n;while(cin >> n){candidates.push_back(n);if(getchar() == '\n'){break;}}int target;cin >> target;vector<bool> used(candidates.size(),false);//默认升序排序sort(candidates.begin(),candidates.end());backtracing(candidates, target, 0, used);//结果输出for (int i = 0; i < result.size(); ++i) {for (int j = 0; j < result[i].size(); ++j) {cout << result[i][j];if(j != result[i].size() - 1) cout << " ";}//注意最后输出不需要换行。if(i == result.size() - 1) return 0;cout << endl;}return 0;
}/* 测试样例
10 1 2 7 6 1 5
82 5 2 1 2
5
*
* */