Files
leetcode-master/problems/0216.组合总和III.md
youngyangyang04 e08fd0eea7 Update
2020-11-24 08:37:52 +08:00

219 lines
8.3 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

> 别看本篇选的是组合总和III而不是组合总和本题和上一篇[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)相比难度刚刚好!
# 第216题.组合总和III
链接https://leetcode-cn.com/problems/combination-sum-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]]
# 思路
本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。
相对于[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)无非就是多了一个限制本题是要找到和为n的k个数的组合而整个集合已经是固定的了[1,...,9]。
想到这一点了,做过[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)之后,本题是简单一些了。
本题k相当于了树的深度9因为整个集合就是9个数就是树的宽度。
例如 k = 2n = 4的话就是在集合[1,2,3,4,5,6,7,8,9]中求 k个数 = 2, n = 4的组合。
选取过程如图:
![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png)
图中可以看出只有最后取到集合13和为4 符合条件。
## 回溯三部曲
* **确定递归函数参数**
和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样依然需要一维数组path来存放符合条件的结果二维数组result来存放结果集。
这里我依然定义path 和 result为全局变量。
至于为什么取名为path从上面树形结构中可以看出结果其实就是一条根节点到叶子节点的路径。
```
vector<vector<int>> result; // 存放结果集
vector<int> path; // 符合条件的结果
```
接下来还需要如下参数:
* targetSumint目标和也就是题目中的n。
* kint就是题目中要求k个数的集合。
* sumint为已经收集的元素的总和也就是path里元素的总和。
* startIndexint为下一层for循环搜索的起始位置。
所以代码如下:
```
vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum, int k, int sum, int startIndex)
```
其实这里sum这个参数也可以省略每次targetSum减去选取的元素数值然后判断如果targetSum为0了说明收集到符合条件的结果了我这里为了直观便于理解还是加一个sum参数。
还要强调一下,回溯法中递归函数参数很难一次性确定下来,一般先写逻辑,需要啥参数了,填什么参数。
* 确定终止条件
什么时候终止呢?
在上面已经说了k其实就已经限制树的深度因为就取k个元素树再往下深了没有意义。
所以如果path.size() 和 k相等了就终止。
如果此时path里收集到的元素和sum 和targetSum就是题目描述的n相同了就用result收集当前的结果。
所以 终止代码如下:
```
if (path.size() == k) {
if (sum == targetSum) result.push_back(path);
return; // 如果path.size() == k 但sum != targetSum 直接返回
}
```
* **单层搜索过程**
本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i<=9
如图:
![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png)
处理过程就是 path收集每次选取的元素相当于树型结构里的边sum来统计path里元素的总和。
代码如下:
```
for (int i = startIndex; i <= 9; i++) {
sum += i;
path.push_back(i);
backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
sum -= i; // 回溯
path.pop_back(); // 回溯
}
```
**别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!**
参照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的模板不难写出如下C++代码:
```
class Solution {
private:
vector<vector<int>> result; // 存放结果集
vector<int> path; // 符合条件的结果
// targetSum目标和也就是题目中的n。
// k题目中要求k个数的集合。
// sum已经收集的元素的总和也就是path里元素的总和。
// startIndex下一层for循环搜索的起始位置。
void backtracking(int targetSum, int k, int sum, int startIndex) {
if (path.size() == k) {
if (sum == targetSum) result.push_back(path);
return; // 如果path.size() == k 但sum != targetSum 直接返回
}
for (int i = startIndex; i <= 9; i++) {
sum += i; // 处理
path.push_back(i); // 处理
backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
sum -= i; // 回溯
path.pop_back(); // 回溯
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
result.clear(); // 可以不加
path.clear(); // 可以不加
backtracking(n, k, 0, 1);
return result;
}
};
```
## 剪枝
这道题目,剪枝操作其实是很容易想到了,想必大家看上面的树形图的时候已经想到了。
如图:
![216.组合总和III1](https://img-blog.csdnimg.cn/2020112319580476.png)
已选元素总和如果已经大于n图中数值为4那么往后遍历就没有意义了直接剪掉。
那么剪枝的地方一定是在递归终止的地方剪,剪枝代码如下:
```
if (sum > targetSum) { // 剪枝操作
return;
}
```
和[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA) 一样for循环的范围也可以剪枝i <= 9 - (k - path.size()) + 1就可以了。
最后C++代码如下:
```
class Solution {
private:
vector<vector<int>> result; // 存放结果集
vector<int> path; // 符合条件的结果
void backtracking(int targetSum, int k, int sum, int startIndex) {
if (sum > targetSum) { // 剪枝操作
return; // 如果path.size() == k 但sum != targetSum 直接返回
}
if (path.size() == k) {
if (sum == targetSum) result.push_back(path);
return;
}
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
sum += i; // 处理
path.push_back(i); // 处理
backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
sum -= i; // 回溯
path.pop_back(); // 回溯
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
result.clear(); // 可以不加
path.clear(); // 可以不加
backtracking(n, k, 0, 1);
return result;
}
};
```
# 总结
开篇就介绍了本题与[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)的区别,相对来说加了元素总和的限制,如果做完[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)再做本题在合适不过。
分析完区别,依然把问题抽象为树形结构,按照回溯三部曲进行讲解,最后给出剪枝的优化。
相信做完本题,大家对组合问题应该有初步了解了。
**就酱如果感觉对你有帮助就帮Carl转发一下吧让更多小伙伴知道这里**
> **我是[程序员Carl](https://github.com/youngyangyang04),可以找我[组队刷题](https://img-blog.csdnimg.cn/20201115103410182.png),也可以在[B站上找到我](https://space.bilibili.com/525438321),本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在公众号:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),关注后就会发现和「代码随想录」相见恨晚!**
**如果感觉对你有帮助,不要吝啬给一个👍吧!**