0-1背包、完全背包算法模板从递归到记忆化搜索到动态规划
不管是0-1背包还是完全背包,我们都可以将问题转换成为选择或者不选的问题,这个问题在前面的回溯算法模板是一样的。
举个栗子:
假如有1、4、5这三个数,问组成和为12的数需要多少个?
这时候就是遍历这个数组,1需不需要 在原来的基础上 4 需不需要 ,5需不需要,经过多次比较之后能得到最终的结果。
题目:
# 递归 + 记忆化搜索(缓存)
class Solution {private int[] coins;private int[][] memo;public int coinChange(int[] coins, int amount) {this.coins = coins;int n = coins.length;memo = new int[n][amount + 1];for (int[] row : memo) {Arrays.fill(row, -1); // -1 表示没有访问过}int ans = dfs(n - 1, amount);return ans < Integer.MAX_VALUE / 2 ? ans : -1;}private int dfs(int i, int c) {if (i < 0) {return c == 0 ? 0 : Integer.MAX_VALUE / 2; // 除 2 防止下面 + 1 溢出}if (memo[i][c] != -1) { // 之前计算过return memo[i][c];}if (c < coins[i]) {return memo[i][c] = dfs(i - 1, c);}return memo[i][c] = Math.min(dfs(i - 1, c), dfs(i, c - coins[i]) + 1);}
}
题目基本上还是完全背包,主要看dfs的内容。
i 依次从数组的最后一个依次往前面走,走到当前这个位置有两种情况,分别是选和不选,如果是选当前的这个情况,由于当前这个硬币拿了之后,下一次仍然可能拿,所以我们下一次的起点仍然是i。另外一种情况是当前这种情况不拿,那么直接进入到下一个状态,也就是可以i - 1.
最后比较这两种状态哪一种状态需要的个数少。
注意点:
- 由于题目要求的是最少的,所以对于一些越界的情况,不可达的情况我们都设置成为最大值,之后这条路就不会进行选择。
- 需要注意第一个判断条件之后返回的情况
下一步:
单纯使用递归进行搜索,肯定会超时,这时候我们会在原来的接触上直接加上一个缓存,用于记录当前这个状态的结果吗,下一次计算这个位置的时候,就不用重新计算,直接进行拿就可以。
如果dfs中用到了几个变量,那么我们设置数组的时候基本上就是几维的。
下一步动态规划:
class Solution {public int coinChange(int[] coins, int amount) {int n = coins.length;int[][] f = new int[n + 1][amount + 1];Arrays.fill(f[0], Integer.MAX_VALUE / 2); // 除 2 防止下面 + 1 溢出f[0][0] = 0;for (int i = 0; i < n; i++) {for (int c = 0; c <= amount; c++) {if (c < coins[i]) f[i + 1][c] = f[i][c];else f[i + 1][c] = Math.min(f[i][c], f[i + 1][c - coins[i]] + 1);}}int ans = f[n][amount];return ans < Integer.MAX_VALUE / 2 ? ans : -1;}
}
到了这里就能发现是大家之前数值的递归方程,我们都会疑惑这个方程是从哪里来的,其实通过查看这个递归方程是不是和上面dfs上面的状态转移方程有点相似,其实是一样的。将我们原来每一步的操作转换成为填表的一个过程。