文章目录
动归五部曲
dp[i]: amount = i 时需要的最小硬币数、
初始化: dp[0] = 0; dp[i] = Integer.MAX_VALUE: ;
递推关系式:先对coins排序 for(int i = 0; j < coins.length && coins[i] <= amount; i ++){ dp[i] = Math.min(dp[i - coins[i]] + 1, dp[i]) }
返回值: return dp[n];
空间优化
题目
代码及提示
import java.util.Arrays;
class Solution {
public int coinChange(int[] coins, int amount) {
/*
dp[i]: amount = i 时需要的最小硬币数、
初始化: dp[0] = 0; dp[i] = Integer.MAX_VALUE: ;
递推关系式:先对coins排序 for(int i = 0; j < coins.length && coins[i] <= amount; i ++){ dp[i] = Math.min(dp[i - coins[i]] + 1, dp[i]) }
返回值: return dp[n];
* */
int[] dp = new int[amount+1];
dp[0] = 0;
for(int i = 1; i<= amount; i++){
dp[i] = Integer.MAX_VALUE - 1;
}
System.out.println(dp[amount]);
Arrays.sort(coins);;
int len = coins.length;
for(int i = 1; i<= amount; i++){
for(int j = 0; j < len && coins[j] <= i ; j++){ // coins[j] <= i && j < coins.length 这么写是错的,当能够走到 && 后面的时候,说明coins[j] <= i 为 true, 此时 j肯定小于coins.length, 若是coins[j] <= i 就越界了,那么就直接报错,也不会走到&& 后面, 所以应该调换二者的位置
dp[i] = Math.min(dp[i - coins[j]] + 1, dp[i]); // 本来 Integer.MAX_VALUE + 1 大于 dp[i] 的,所以dp[i] 不会变, 但是,由于发生上溢出,Integer.MAX_VALUE + 1 变成了负数,所以小于dp[i], dp[i]就变成 -2147483648
}
}
if(dp[amount] == Integer.MAX_VALUE - 1){ // !!!这种情况,说明没有硬币能组成它,没有满足条件的答案,
return -1;
}
return dp[amount];
}
}
// @solution-sync:end
class Main {
public static void main(String[] args) {
int[] coins = new int[]{2};
int amount = 3;
int result = new Solution().coinChange(coins, amount);
System.out.println(result);
}
}
背包思路(一维代码)(其实本质上就是 “分组的动态规划”,因为第一层for循环是从1开始(即最小的子问题往最大的子问题走的))
后面的二维背包思路,就不是从最小的子问题往最大我子问题,他是从从最大的子问题往最小的子问题(一开始还需要复杂的初始化),二维解法不能算是动态规划。
背包思路: 就是如果第一个物品重量小于背包容量,就依次把这个物品,放入动态规划的所有子背包 dp[i] (通过一个比较dp[i] = Math.min(dp[i], dp[i - coin] + value);); 其中i 的最大值为背包容量amount, 然后在考虑第二个物品能不能放,能放的话,就初始化所有能放得下的子背包
import java.util.Arrays;
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
dp[0] = 0;
for(int i = 1; i<= amount; i++){
dp[i] = Integer.MAX_VALUE - 1;
}
for (int coin : coins)
{
for (int i = 0; i <= amount; i++)
{
if (coin <= i)
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
return dp[amount] == Integer.MAX_VALUE-1 ? -1 : dp[amount];
}
}
// @solution-sync:end
class Main {
public static void main(String[] args) {
int[] coins = new int[]{2};
int amount = 3;
int result = new Solution().coinChange(coins, amount);
System.out.println(result);
}
}
背包思路 对应的 二维的代码
重点1
if (j < weight[i]) // 这里的weight[i] 就像动态规划思路里的coins[i], 这个就像第二层for循环里面的继续条件的反方面(即else语句里面就是for循环里面的正向条件)coins[j] <= i
dp[i][j] = dp[i - 1][j];
重点2
else // 这里就像第二层for循环判断能够继续for循环之后(else对应for循环能够继续的条件coins[j] <= i ),for循环里面的操作
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
void bags()
{
vector<int> weight = {1, 3, 4}; //各个物品的重量
vector<int> value = {15, 20, 30}; //对应的价值
int bagWeight = 4; //背包最大能放下多少重的物品
// 二维数组:状态定义:dp[i][j]表示从0-i个物品中选择不超过j重量的物品的最大价值
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));
// 初始化:第一列都是0,第一行表示只选取0号物品最大价值
for (int j = bagWeight; j >= weight[0]; j--)
dp[0][j] = dp[0][j - weight[0]] + value[0];
// weight数组的大小 就是物品个数
for (int i = 1; i < weight.size(); i++) // 遍历物品(第0个物品已经初始化)
{
for (int j = 0; j <= bagWeight; j++) // 遍历背包容量
{
if (j < weight[i]) // 这个就像第二层for循环里面的继续条件coins[j] <= i //背包容量已经不足以拿第i个物品了
dp[i][j] = dp[i - 1][j]; //最大价值就是拿第i-1个物品的最大价值
//背包容量足够拿第i个物品,可拿可不拿:拿了最大价值是前i-1个物品扣除第i个物品的 重量的最大价值加上i个物品的价值
//不拿就是前i-1个物品的最大价值,两者进行比较取较大的
else // 这里就像第二层for循环判断能够继续for循环之后,for循环里面的操作
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagWeight] << endl;
}