Files
leetcode-master/problems/0377.组合总和IV二维dp数组.md
programmercarl 122a8e6161 Update
2023-02-25 08:43:56 +08:00

6.3 KiB
Raw Blame History

完全背包的排列问题模拟

Problem

  1. 排列问题是完全背包中十分棘手的问题。
  2. 其在迭代过程中需要先迭代背包容量,再迭代物品个数,使得其在代码理解上较难入手。

Contribution

本文档以力扣上组合总和IV为例提供一个二维dp的代码例子并提供模拟过程以便于理解

Code

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}。