文章目录
- 1. 一和零(474)
- 2. 盈利计划(879)
1. 一和零(474)
题目描述:
状态表示:
我们之前的01背包问题以及完全背包问题都是一维的,因为我们只有一个要求或者说是限制那就是背包的容量,但是这里不同这题有两个限制,其实和一个限制是类似的,只不过给数组多加上一维而已。因此我们建立三维数组dp[i][j][k]表示我们在前i个二进制字符串中选择,要求选中的字符串中的0以及1字符的总数分别不能超过i以及j时strs最大子集的长度。
状态转移方程:
这题因为每个二进制字符串只能选一次,实际上就是01背包问题。我们对选择的第i个二进制字符串进行分析,如果不选择该字符串,那么dp[i][j][k]=dp[i-1][j][k].如果我们选择第i个二进制字符串,那么在j>=此二进制字符串0的个数时并且k>=此字符串中1的个数时,dp[i][j][k]=dp[i-1][j-此二进制字符串0的个数][k-此字符串中1的个数]+1。
初始化:
根据前面我们01背包以及完全背包的做题经验我们初始化会给ijk三个维度分别加上第0列/行(三维不知如何形容),对于j和k维度我们直接加入运算,不去考虑它初始化的问题,对于i维度的第0行因为此时意味着没有字符串,所以无论j、k维度如何要求最终数组的对应结果也就是字符串数组的对应的子集长度都是0,所以当i等于0时,j和k对应的平面上的dp值直接全赋为0即可。
填表顺序:
i维度下标从小到大进行运算,j和k维度随意。
返回值:
初始化dp数组时,ijk三个维度长度分别设为len m和n,因此返回值为dp[len][m][n]
代码如下:
public int findMaxForm(String[] strs, int m, int n) {
int len = strs.length;
int[][][] dp = new int[len + 1][m + 1][n + 1];
for (int i = 1; i <= len; i++) {
int count0 = 0;
int count1 = 0;
for (char ch : strs[i - 1].toCharArray()) {
if (ch == '0') {
count0++;
} else {
count1++;
}
}
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= n; k++) {
dp[i][j][k] = dp[i - 1][j][k];
if (j - count0 >= 0 && k - count1 >= 0) {
dp[i][j][k] = Math.max(dp[i][j][k], dp[i - 1][j - count0][k - count1] + 1);
}
}
}
}
return dp[len][m][n];
}
代码运行效率:
优化后代码如下:
这里优化要注意一个问题,这题近似于一个01背包问题,所以jk两个维度下标都需要从大到小进行运算。
public int findMaxForm(String[] strs, int m, int n) {
int len = strs.length;
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= len; i++) {
int count0 = 0;
int count1 = 0;
for (char ch : strs[i - 1].toCharArray()) {
if (ch == '0') {
count0++;
} else {
count1++;
}
}
for (int j = m; j >= count0; j--) {
for (int k = n; k >= count1; k--) {
dp[j][k] = Math.max(dp[j][k], dp[j - count0][k - count1] + 1);
}
}
}
return dp[m][n];
}
优化后运行效率:
题目链接
时间复杂度:O(N^3)
空间复杂度:O(N^3)
优化后空间复杂度:O(N^2)
2. 盈利计划(879)
题目描述:
状态表示:
和上一题类似,这题也是多加了一个限制条件的01背包问题,所以建立三维数组dp,使用dp[i][j][k]来表示在前i个计划中选择,要求人员数不大于j并且利润不低于k时的满足条件的最多选法。
状态转移方程:
状态分析也是和01背包问题是类似的,就是对第i个计划的选择来进行分析,当不选该计划时dp[i][j][k]=dp[i-1][j][k].当选择该计划时并且满足j>=group[i]时,dp[i][j][k]=dp[i][j-group[i]][max(0,k-profit[i])],这里解释一下第三个维度,因为第三个维度就是要求利润要不少于k,所以k-profit[i]即使小于0也没有关系。因为此时可以得到不等式k<profit[i],我们状态表示是计划加起来的利润要大于等于k,此时光加入profit[i]都已经大于k了,我们自然希望加起来的利润越大越好,但是因为数组中的下标值不能为0,所以如果纬度值小于0直接取0即可。
初始化:
为了避免数组越界问题以及方便初始化,对dp数组的三个维度都加上一行/列,对于j和k这两个维度因为这题实际上也是一个01背包问题,所以直接不初始化加入运算即可。当i维度等于0时,也就是i等于0时的那个平面,因为此时没有计划,所以没有利润以及人员的需求,此时当无论j取多少这个条件都是满足的,但是因为利润至少为k,所以只有当k==0时会有一种选法。综上对dp[0][j][0]这一列/行全赋为1,其余初始化为0即可。
填表顺序:
对于i要从小到大进行运算,j和k下标顺序随意。
返回值:
初始化三维数组时形式如dp[len+1][n+1][minProfit+1],所以返回dp[len][n][minProfit]即可。
代码如下:
class Solution {
public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
int len = group.length;
int TEMP = (int) 1e9 + 7;
int[][][] dp = new int[len + 1][n + 1][minProfit + 1];
for (int i = 0; i <= n; i++) {
dp[0][i][0] = 1;
}
for (int i = 1; i <= len; i++) {
for (int j = 0; j <= n; j++) {
for (int k = 0; k <= minProfit; k++) {
dp[i][j][k] = dp[i - 1][j][k];
if (j >= group[i - 1]) {
dp[i][j][k] += dp[i - 1][j - group[i - 1]][Math.max(0, k - profit[i - 1])];
dp[i][j][k] %= TEMP;
}
}
}
}
return dp[len][n][minProfit];
}
}
代码运行效率:
优化后代码如下:
class Solution {
public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
int len = group.length;
int TEMP = (int) 1e9 + 7;
int[][] dp = new int[n + 1][minProfit + 1];
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}
for (int i = 1; i <= len; i++) {
for (int j = n; j >= group[i - 1]; j--) {
for (int k = minProfit; k >= 0; k--) {
dp[j][k] += dp[j - group[i - 1]][Math.max(0, k - profit[i - 1])];
dp[j][k] %= TEMP;
}
}
}
return dp[n][minProfit];
}
}
优化后运行效率:
题目链接
时间复杂度:O(N^3)
空间复杂度:O(N^3)
优化后空间复杂度:O(N^2)