1. 题目来源
相关:
2. 题目解析
完全背包求具体方案的问题,是 [01背包] 背包问题求具体方案(01背包+求方案数+思维) 的变种。
首先如果要使满足要求的拼的数最大,有两个要求:
- 位数尽量大
- 位数相同,字典序最大
有限制的组合问题求最优解,一般会对应到背包问题。 在此就是从 1~9 中选,要求花费总和等于 target
,让拼出来的数最大。
抽象为背包问题,背包容量为 target
,9 个物品,物品体积为 cost[i]
,物品价值为 1。
故问题转化为,给定 9 个物品,一个背包,求在恰好装满背包的情况下,总价值最大的所有方案中字典序最大的一个方案。 至此,已经完全转化为 [01背包] 背包问题求具体方案(01背包+求方案数+思维)。
但是,背包问题中细节很多,背包恰好装满、至多装满、物品次数限制等。在循环、初始化方面有所不同。
完全背包、01 背包的状态转移方程对比:
f[i][j] 表示前 i 个物品恰好装 j 体积的最大价值
完全背包:f[i][j]=max(f[i-1][j], f[i][j-cost[i]]+1);
01背包:f[i][j]=max(f[i-1][j], f[i-1][j-cost[i]]+1);
这样就能求出最大价值,即最大的位数。求方案就是倒推一遍即可,这个方案就是字典序最大的方案,即要求我们尽可能选 9,如果不能选 9 再选 8,同理往后选即可。
答案为 f[9][target]
,它有两种转移情况:
- 不选 9,则
f[9][target]=f[8][target]
。 - 选 9 则
f[9][target]=f[9][target-cost[9]]+1
。 - 即只需要从后往前进行倒推即可,当选 9 和不选 9 都一样时,我们要将 9 选上,保证字典序尽量大。
注意本题的初始化方式,本题背包定义是前 i
个物品恰好装满 j
体积的最大价值,故当一个物品都没有时,它是没有体积的,无法恰好装满 j
体积,自然没有价值
初始化时,f[0][1~target]
均是非法方案,不能像以往一样直接将其置为 0,而是要将其置为 -INF
,视为非法情况。
时间复杂度: O ( n m ) O(nm) O(nm) 物品数量 x 最大容量=9 * 5000
空间复杂度: O ( n m ) O(nm) O(nm)
class Solution {
public:
string largestNumber(vector<int>& cost, int target) {
vector<vector<int>> f(10, vector<int>(target + 1));
// 本题保证恰好装满背包,即 0 物品时,体积一定是 0,体积不为 0 则均是非法情况,赋为最小值即可
for (int i = 1; i <= target; i ++ ) f[0][i] = -1e8;
for (int i = 1; i <= 9; i ++ )
for (int j = 0; j <= target; j ++ ) {
f[i][j] = f[i - 1][j];
if (j >= cost[i - 1]) f[i][j] = max(f[i][j], f[i][j - cost[i - 1]] + 1);
}
// 无解情况,1 个数都没选
if (f[9][target] < 1) return "0";
// 倒推求方案,保证字典序最大
string res;
for (int i = 9, j = target; i; i -- ) {
while (j >= cost[i - 1] && f[i][j] == f[i][j - cost[i - 1]] + 1) {
res += to_string(i);
j -= cost[i - 1];
}
}
return res;
}
};