背包问题
1.0-1背包问题
问题描述:
有n件物品,每件物品的重量为w[i],价值为c[i],选取物品放进容量为V的背包,使得背包内物品总价值最大,每件物品只有1件。
解决方法:
令dp[i][v]表示前i件物品恰好装入容量为V的背包所能获得的最大价值。
1.若不放第i件物品,转换为前i-1件物品放入v的最大价值,即dp[i-1][v]。
2.若放第i件物品,转为前i-1件物品放入v-w[i]的最大价值,即dp[i-1][v-w[i]]+c[i]。
状态转移方程:
二维:dp[i][v]=max(dp[i-1][v], dp[i-1][v-w[i]]+c[i]) (w[i]=<v<=V)
注意到,dp[i][v]只和前一个状态i-1有关,因此可以把二维数组变换为一维数组:
一维:dp[v]=max(dp[v], dp[v-w[i]]+c[i]) (w[i]=<v<=V)
使用二维方程时,v的枚举正序逆序都可以,而一维方程的枚举必须是逆序的(从大到小枚举),即
for(int i = 1; i <= n; i++) {
for(int v = V; v >= w[i];v--) { // 逆序
// 状态转移方程;
// 如果是正序的话,dp[i][v]的更新涉及到dp[i-1][v-w[i]],由于是一维数组,dp[i-1][v-w[i]]已经被更新为dp[i][v-w[i]],不是我们想要的i-1时的状态,而逆序不会出现这个问题。
}
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];
for(int i = 0; i < strs.length; i++) {
// 得到0、1数量
int[] k = get_zero_one(strs[i]);
for(int v0 = m; v0 >= k[0]; v0--) {
for(int v1 = n; v1 >= k[1]; v1--) {
dp[v0][v1] = Math.max(dp[v0][v1], dp[v0-k[0]][v1-k[1]] + 1);
}
}
}
return dp[m][n];
}
int[] get_zero_one(String s) {
int count[] = new int[2];
for(int i = 0; i < s.length(); i++) {
if(s.charAt(i) == '0') {
count[0]++;
}else {
count[1]++;
}
}
return count;
}
}
2.完全背包问题
问题描述:
有n件物品,每件物品的重量为w[i],价值为c[i],选取物品放进容量为V的背包,使得背包内物品总价值最大,每件物品有无数件。
解决方法:
令dp[i][v]表示前i件物品恰好装入容量为V的背包所能获得的最大价值。
1.若不放第i件物品,转换为前i-1件物品放入V的最大价值,即dp[i-1][v]。
2.若放第i件物品,由于可以取无数件物品,转为前i-1件物品放入V-w[i]的最大价值,即dp[i][V-w[i]]+c[i]。
状态转移方程:
dp[i][v]=max(dp[i-1][v], dp[i][v-w[i]]+c[i]) (w[i] =<v<= V)
同样,可以改写为一维:
dp[v] = max(dp[v],dp[v-w[i]]+c[i]) (w[i] =<v<= V)
这里v的枚举必须为正序。因为dp[i][v]和dp[i][V-w[i]]有关,必须先正序算出dp[i][v-w[i]]才能得到dp[i][v]。
例题:
494. 目标和
322. 零钱兑换
零钱兑换题解:
思路和完全背包问题一样,只是dp定义为前i个硬币金额为v时所需的最少硬币数量,我们可以先定义二维数组dp[i][v]=min(dp[i-1][v], dp[i][v-w[i]]+1),不选当前硬币,状态为dp[i-1][v],选择当前硬币,硬币数量多1,状态为dp[i][v-w[i]]+1,取其中的最小值,同样,二维数组可以省略为一维数组,且正序枚举v。
注意点:取最小值因此dp数组初始化时要用一个较大值初始化,当总金额为0时,硬币数量可为0,因此dp[0]=0。
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1]; // 金额为v所需的最少硬币个数
Arrays.fill(dp, 10010);
dp[0] = 0; // 金额为0时最少为0
for(int i = 0; i < coins.length; i++) {
for(int v = coins[i]; v <= amount; v++) {
dp[v] = Math.min(dp[v], dp[v-coins[i]] + 1);
}
}
if(dp[amount] != 10010) {
return dp[amount];
}else {
return -1;
}
}
}
正方形问题:
dp(i, j)表示以i,j为顶点的最大正方形xx数。
221. 最大正方形
1277. 统计全为 1 的正方形子矩阵
1139. 最大的以 1 为边界的正方形
数组问题:
152. 乘积最大子数组