104 lines
4.2 KiB
Markdown
104 lines
4.2 KiB
Markdown
# 思路
|
||
|
||
本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,**其实就是求排列!**
|
||
|
||
弄清什么是组合,什么是排列很重要。
|
||
|
||
组合不强调顺序,(1,5)和(5,1)是同一个组合。
|
||
|
||
排列强调顺序,(1,5)和(5,1)是两个不同的排列。
|
||
|
||
大家在学习回溯算法系列的时候,一定做过这两道题目[回溯算法:39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)和[回溯算法:40.组合总和II](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)
|
||
|
||
大家会感觉很像,但其本质是本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来。
|
||
|
||
如果本题要把排列都列出来的话,只能使用回溯算法爆搜。
|
||
|
||
|
||
* 确定dp数组以及下标的含义
|
||
|
||
dp[i]: 凑成目标正整数为i的组合个数为dp[i]
|
||
|
||
* 确定递推公式
|
||
|
||
dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导出来。
|
||
|
||
因为只要得到nums[j],排列个数dp[i - nums[j]],就是dp[i]的一部分。
|
||
|
||
那题目中的示例来例子,target=4,那么dp[4] 是求和为4的排列个数,这个排列个数一定等于以元素3为结尾的排列的个数,以元素2为结尾的排列的个数,以元素1为结尾的排列的个数 之和!
|
||
|
||
以元素3为结尾的排列的个数就是dp[1](dp[4 - 3]),以元素2为结尾的排列的个数就是dp[2](dp[4 - 2]),以元素1为结尾的排列的个数就是dp[3](dp[4 - 1])。
|
||
|
||
dp[4] 就等于 dp[1],dp[2],dp[3]之和
|
||
|
||
所以dp[i] 就是 dp[i - nums[j]]之和,而nums[j]就是 1, 2, 3。
|
||
|
||
此时不难理解,递归公式为:dp[i] += dp[i - nums[j]];
|
||
|
||
* dp数组如何初始化
|
||
|
||
因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。
|
||
|
||
非0下标的dp[i]初始化为0,这样才不会影响dp[i]累加所有的dp[i-nums[j]]
|
||
|
||
* 确定遍历顺序
|
||
|
||
个数可以不限使用,这是一个完全背包,且得到的集合是排列(需要考虑元素之间的顺序)。
|
||
|
||
所以将target放在外循环,将nums放在内循环,内循环从前到后遍历。
|
||
|
||
本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了。
|
||
|
||
需要把target放在外循环,将nums放在内循环,为什么呢?
|
||
|
||
还是拿本题示例来举例,只有吧遍历target放在外面,dp[4] 才能得到 以元素3为结尾的排列的个数dp[1],以元素2为结尾的排列的个数就是dp[2],以元素1为结尾的排列的个数就是dp[3] 之和。
|
||
|
||
那么以元素3为结尾的排列的个数dp[1] 其实就已经包含了元素1了,而以元素1为结尾的排列的个数就是dp[3]也已经包含了元素3。
|
||
|
||
所以这两个就算成了两个集合了,即:排列。
|
||
|
||
如果把遍历nums放在外循环,遍历target的作为内循环的话呢
|
||
|
||
举一个例子:计算dp[4]的时候,结果集只有 (1,3) 这样的集合,不会有(3,1)这样的集合,因为nums遍历放在外层,3只能出现在1后面!
|
||
|
||
所以本题遍历顺序最终遍历顺序:target放在外循环,将nums放在内循环,内循环从前到后遍历。
|
||
|
||
* 举例来推导dp数组
|
||
|
||
我们再来用示例中的例子推导一下:
|
||
|
||
dp[0] = 1
|
||
dp[1] = dp[0] = 1
|
||
dp[2] = dp[1] + dp[0] = 2
|
||
dp[3] = dp[2] + dp[1] + dp[0] = 4
|
||
dp[4] = dp[3] + dp[2] + dp[1] = 7
|
||
|
||
如果代码运行处的结果不是想要的结果,就把dp[i]都打出来,看看和我们推导的一不一样。
|
||
|
||
经过以上的分析,C++代码如下:
|
||
|
||
```
|
||
class Solution {
|
||
public:
|
||
int combinationSum4(vector<int>& nums, int target) {
|
||
vector<int> dp(target + 1, 0);
|
||
dp[0] = 1;
|
||
for (int i = 0; i <= target; i++) {
|
||
for (int j = 0; j < nums.size(); j++) {
|
||
if (i - nums[j] >= 0) {
|
||
dp[i] += dp[i - nums[j]];
|
||
}
|
||
}
|
||
}
|
||
return dp[target];
|
||
}
|
||
};
|
||
```
|
||
|
||
C++测试用例有超过两个树相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]。
|
||
|
||
但java就不用考虑这个限制,我理解java里的int也是四个字节吧,也有可能leetcode后台对不同语言的测试数据不一样。
|
||
|
||
|
||
|