leetcode-master/problems/0377.组合总和Ⅳ.md

104 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# 思路
本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,**其实就是求排列!**
弄清什么是组合,什么是排列很重要。
组合不强调顺序,(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后台对不同语言的测试数据不一样