leetcode-master/problems/0377.组合总和IV二维dp数组.md

146 lines
6.3 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.

# 完全背包的排列问题模拟
#### Problem
1. 排列问题是完全背包中十分棘手的问题。
2. 其在迭代过程中需要先迭代背包容量,再迭代物品个数,使得其在代码理解上较难入手。
#### Contribution
本文档以力扣上[组合总和IV](https://leetcode.cn/problems/combination-sum-iv/)为例提供一个二维dp的代码例子并提供模拟过程以便于理解
#### Code
```cpp
int combinationSum4(vector<int>& nums, int target) {
// 定义背包容量为target物品个数为nums.size()的dp数组
// dp[i][j]表示将第0-i个物品添加入排列中和为j的排列方式
vector<vector<int>> dp (nums.size(), vector(target+1,0));
// 表示有0,1,...,n个物品可选择的情况下和为0的选择方法为1什么都不取
for(int i = 0; i < nums.size(); i++) dp[i][0] = 1;
// 必须按列遍历,因为右边数组需要知道左边数组最低部的信息(排列问题)
// 后面的模拟可以更清楚的表现这么操作的原因
for(int i = 1; i <= target; i++){
for(int j = 0; j < nums.size(); j++){
// 只有nums[j]可以取的情况
if(j == 0){
if(nums[j] > i) dp[j][i] = 0;
// 如果背包容量放不下 那么此时没有排列方式
else dp[j][i] = dp[nums.size()-1][i-nums[j]];
// 如果背包容量放的下 全排列方式为dp[最底层][容量-该物品容量]排列方式后面放一个nums[j]
}
// 有多个nums数可以取
else{
// 如果背包容量放不下 那么沿用0-j-1个物品的排列方式
if(nums[j] > i) dp[j][i] = dp[j-1][i];
// 如果背包容量放得下 在dp[最底层][容量-该物品容量]排列方式后面放一个nums[j]后面放个nums[j]
// INT_MAX避免溢出
else if(i >= nums[j] && dp[j-1][i] < INT_MAX - dp[nums.size()-1][i-nums[j]])
dp[j][i] = dp[j-1][i] + dp[nums.size()-1][i-nums[j]];
}
}
}
// 打印dp数组
for(int i = 0; i < nums.size(); i++){
for(int j = 0; j <= target; j++){
cout<<dp[i][j]<<" ";
}
cout<<endl;
}
return dp[nums.size()-1][target];
}
```
#### Simulation
##### 样例 nums = [2,3,4], target = 6
##### 1. 初始化一个3x7的dp数组
1 0 0 0 0 0 0
1 0 0 0 0 0 0
1 0 0 0 0 0 0
dp\[0-2\]\[0\] = 1含义是有nums[0-2]物品时使得背包容量为0的取法为1作用是在取到nums[i]物品使得背包容量为nums[i]时取法为1。
##### 2.迭代方式
必须列优先,因为右边的数组在迭代时需要最左下的数组最终结果。
##### 3. 模拟过程
i = 1, j = 0 dp\[0\]\[1\] = 0表示在物品集合{2}中无法组成和为1
i = 1, j = 1 dp\[1\]\[1\] = 0表示在物品集合{2,3}中无法组成和为1
i = 1, j = 2 dp\[2\]\[1\] = 0表示在物品集合{2,3,4}中无法组成和为1
1 0 0 0 0 0 0
1 0 0 0 0 0 0
1 **0** 0 0 0 0 0
此时dp\[2\]\[1\]作为第1列最底部的元素表示所有物品都有的情况下组成和为1的排列方式为0
————————————————————————————
i = 2, j = 0 dp\[0\]\[2\] = 1表示在物品集合{2}中取出和为2的排列有{2}
i = 2, j = 1 dp\[1\]\[2\] = 1表示在物品集合{2,3}中取出和为2的排列有{2}
i = 2, j = 2 dp\[2\]\[2\] = 1表示在物品集合{2,3,4}中取出和为2的排列有{2}
1 0 1 0 0 0 0
1 0 1 0 0 0 0
1 0 **1** 0 0 0 0
此时dp\[2\]\[2\]作为第2列最底部的元素表示所有物品都有的情况下和为2的排列方式有1个 类比成一维dp即dp[2]=dp[0]
————————————————————————————
i = 3, j = 0 dp\[0\]\[3\] = 0表示在物品集合{2}中无法取出和为3
i = 3, j = 1 dp\[1\]\[3\] = 1表示在物品集合{2,3}中取出和为3的排列有{3}
i = 3, j = 2 dp\[2\]\[3\] = 1表示在物品集合{2,3,4}中取出和为3的排列有{3}
1 0 1 0 0 0 0
1 0 1 1 0 0 0
1 0 1 **1** 0 0 0
此时dp\[2\]\[3\]作为第3列最底部的元素表示所有物品都有的情况下和为3的排列方式有1个类比成一维dp即dp[3]=dp[0]
————————————————————————————
i = 4, j = 0 dp\[0\]\[4\] = 1表示在物品集合{2}中取出和为4的排列有在原有的排列{2}后添加一个2成为{2,2}从第2列底部信息继承获得
i = 4, j = 1 dp\[1\]\[4\] = 1表示在物品集合{2,3}中取出和为4的排列有{2,2}
i = 4, j = 2 dp\[2\]\[4\] = 2表示在物品集合{2,3,4}中取出和为4的排列有{{2,2},{4}}{2,2}的信息从该列头上获得)
1 0 1 0 1 0 0
1 0 1 1 1 0 0
1 0 1 1 **2** 0 0
此时dp\[2\]\[4\]作为第4列最底部的元素表示所有物品都有的情况下和为4的排列方式有2个
————————————————————————————
i = 5, j = 0 dp\[0\]\[5\] = 1表示在物品集合{2}中取出和为5的排列有{3,2} **(3的信息由dp[2]\[3]获得即将2放在3的右边)**
i = 5, j = 1 dp\[1\]\[5\] = 2表示在物品集合{2,3}中取出和为5的排列有{{2,3},{3,2}} **({3,2}由上一行信息继承,{2,3}是从dp[2] [2]获得将3放在2的右边)**
i = 5, j = 2 dp\[2\]\[5\] = 2表示在物品集合{2,3,4}中取出和为5的排列有{{2,3},{3,2}}
1 0 1 0 1 1 0
1 0 1 1 1 2 0
1 0 1 1 2 **2** 0
此时dp\[2\]\[5\]作为第5列最底部的元素表示所有物品都有的情况下和为5的排列方式有2个
————————————————————————————
i = 6, j = 0 dp\[0\]\[6\] = 2表示在物品集合{2}中取出和为6的排列有{{2,2,2},{4,2}} **(信息由dp[2]\[4]获得即将2放在{2,2}和{4}的右边)**
i = 6, j = 1 dp\[1\]\[6\] = 3表示在物品集合{2,3}中取出和为6的排列有{{2,2,2},{4,2},{3,3}} **({2,2,2},{4,2}由上一行信息继承,{3,3}是从dp[2] [3]获得将3放在3的右边)**
i = 6, j = 2 dp\[2\]\[6\] = 4表示在物品集合{2,3,4}中取出和为6的排列有{{2,2,2},{4,2},{3,3},{2,4}} **({2,2,2},{4,2},{3,3}由上一行继承,{2,4}从dp[2]获得将4放在2的右边)**
1 0 1 0 1 1 2
1 0 1 1 1 2 3
1 0 1 1 2 2 **4**
此时dp\[2\]\[6\]作为第6列最底部的元素表示所有物品都有的情况下和为6的排列方式有4个为{2,2,2}{4,2}{3,3}{2,4}。