一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的层序遍历及相关题目
十 四、二叉树篇之二叉树的属性相关题目
十 五、 二叉树篇之二叉树的修改与构造
十 六、 二叉树篇之二叉搜索树的属性
十 七、二叉树篇之公共祖先问题
十 八、二叉树篇之二叉搜索树的修改与构造
十 九、回溯算法篇之组合问题
二 十、回溯算法篇之分割、子集、全排列问题
二十一、贪心算法篇之入门题目
二十二、贪心算法篇之进阶题目
二十三、动态规划篇之基础题目
二十四、动态规划篇之背包问题:01背包
更新中 … …
刷题路线来自 :代码随想录
一个商品只能放入一次是01背包,而完全背包每个商品的数量没有数量限制,也就是可以放入背包多次
如:
dp 数组:
Lintcode 链接(Leetcode 上没有原题)
给定 n 种物品, 每种物品都有无限个. 第 i 个物品的体积为 A[i], 价值为 V[i].再给定一个容量为 m 的背包. 问可以装入背包的最大价值是多少?
题解:
因为每个物品可以多次放入,所以在放的下的情况下,若要放下新物品的最大价值为 dp[i][j - A[i - 1]] + V[i - 1]),不同与 01 背包,这里是 dp[i][] 而不是 dp[i - 1][],表示在放下新物品的本层dp 数组中寻找放下新物品后剩下的空间的最大价值。
public class Solution {
public int backPackIII(int[] A, int[] V, int m) {
// write your code here
int row = V.length;
int[][] dp = new int[row + 1][m + 1];
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= m; j++) {
if (j >= A[i - 1]) {
// 放得下,注意这里为 ≥
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - A[i - 1]] + V[i - 1]);
} else {
// 放不下
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[row][m];
}
}
优化(一维滚动 dp 数组):
不同于 01 背包,这里遍历背包时是从前向后遍历,因为以一个物品可以放入多次
public class Solution {
public int backPackIII(int[] A, int[] V, int m) {
// write your code here
int row = V.length;
int[] dp = new int[m + 1];
for (int i = 0; i < row; i++) { // 遍历物品
for (int j = A[i]; j <= m; j++) { // 遍历背包,从A[i],开始因为前边放不下,数组中的最大价值为不放入的值,不用改变
dp[j] = Math.max(dp[j], dp[j - A[i]] + V[i]);
}
}
return dp[m];
}
}
Leetcode 链接
题解:
组合问题
第二层的循环遍历背包为从前向后遍历,下标从 coins[i] 开始,因为 coins[i] 之前放不下新物品,数组值不改变。
class Solution {
public int change(int amount, int[] coins) {
// 最大组合
int row = coins.length;
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int i = 0; i < row; i++) {
// 从前向后遍历
for (int j = coins[i]; j <= amount; j++) {
dp[j] = dp[j] + dp[j - coins[i]];
}
}
return dp[amount];
}
}
Leetcode 链接
题解:
排列问题,不同于前边的组合问题不强调顺序。(1,5)和(5,1)是同一个组合,但是是两个不同的排列。这里需要满足以下两个条件
class Solution {
public int combinationSum4(int[] nums, int target) {
int row = nums.length;
int[] dp = new int[target + 1];
dp[0] = 1;
for (int j = 1; j <= target; j++) {
for (int i = 0; i < row; i++) {
if (j >= nums[i]) {
dp[j] = dp[j] + dp[j - nums[i]];
}
}
}
return dp[target];
}
}
Leetcode 链接
将题中的可以爬 1 或 2 个台阶改为可以爬 1 或 2 或 … 或 m 个台阶。(力扣无原题)
题解:
排列问题
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
int m = 2; // m 表示每次可趴 1 - m 阶,这里为 1、2
for (int j = 1; j <= n; j++) { // 先遍历背包
for (int i = 1; i <= m; i++) { // 再遍历物品
if (j >= i) {
dp[j] = dp[j] + dp[j - i];
}
}
}
return dp[n];
}
}
Leetcode 链接
题解:
背包大小为 amount,求最放满背包物品的最少数量
递推公式:Math.min(放入前最少硬币数,放入后最少硬币数)
放入后最少硬币数 = 1 + 放入后背包剩余空间能放入的最少硬币数
初始化: 除了0下标位置外,全部初始化为Integer的最大值max,保证第一个硬币的遍历正常
难点: 怎么判断有满足背包大小的硬币组合吗? 如:[2, 5] amount = 3,返回 -1
class Solution {
public int coinChange(int[] coins, int amount) {
int row = coins.length;
int max = Integer.MAX_VALUE;
int[][] dp = new int[row + 1][amount + 1];
// dp[0][i] 中除dp[0][0] 为 0外,全初始化为 max
// 在背包刚好装下新物品时,1 + dp[i][0] = 1
for (int i = 1; i <= amount; i++) {
dp[0][i] = max;
}
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= amount; j++) {
if (j >= coins[i - 1] && (dp[i][j - coins[i - 1]] != max)) {
// 如果背包能放下新物品,并且背包剩余大小有满足的组合
dp[i][j] = Math.min(dp[i - 1][j], 1 + dp[i][j - coins[i - 1]]);
} else {
// 放不下或者 dp[i][j - coins[i - 1]] = max,表示无满足剩余背包大小的组合
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[row][amount] == max ? -1 : dp[row][amount];
}
}
空间优化:
class Solution {
public int coinChange(int[] coins, int amount) {
int row = coins.length;
int max = Integer.MAX_VALUE;
int[] dp = new int[amount + 1];
// 除dp[0] 外 都为max,dp[0] = 0
for (int i = 1; i <= amount; i++) {
dp[i] = max;
}
for (int i = 0; i < row; i++) {
for (int j = coins[i]; j <= amount; j++) {
if ((dp[j - coins[i]] != max)) {
// 放下后,有满足背包剩余大小的组合
dp[j] = Math.min(dp[j], 1 + dp[j - coins[i]]);
}
}
}
return dp[amount] == max ? -1 : dp[amount];
}
}
Leetcode 链接
题解:
同上题,这里的物品不同。背包大小为 n, 用数量不限的完全平方数大小的物品装满
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
int max = Integer.MAX_VALUE;
for (int i = 1; i <= n; i++) {
dp[i] = max;
}
for (int i = 1; i * i <= n; i ++) {
for (int j = i * i; j <= n; j++) {
// 因为物品大小是从最小的 1 开始,所以不存在没有满足背包大小的组合情况, N 个1可以放满任何大小的背包,这里的if 语句可以省略
//if (dp[j - i * i] != max)
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
}
}
return dp[n];
}
}
Leetcode 链接
题解:
dp[i]:[0, i] 的子串被分割后能在字典中找到
递推公式:dp[j] = dp[i] && 子串 [i , j] 能在字典中找到
class Solution {
public static boolean wordBreak(String s, List<String> wordDict) {
//Set set = new HashSet<>(wordDict);
int len = s.length();
boolean[] dp = new boolean[len + 1];
dp[0] = true;
for (int j = 1; j <= len; j++) {
for (int i = 0; i < j; i++) {
dp[j] = dp[i] && wordDict.contains(s.substring(i, j));
if (dp[j]) break;
}
}
return dp[len];
}
}
物品的最大价值(和)
能放入时最大价值在放入新物品前和 放入新物品后产生的两种结果中取最大值
01 背包:
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - A[i - 1]] + V[i - 1])
dp[j] = Math.max(dp[j], dp[j - A[i]] + V[i]) (遍历背包时从后向前遍历)
完全背包:
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - A[i - 1]] + V[i - 1])
dp[j] = Math.max(dp[j], dp[j - A[i]] + V[i]) (遍历背包时从前向后遍历)
背包中物品的组合数
能放入时组合数等于放入新物品前组合数加 放入新物品后组合数
01 背包:
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];
dp[j] = dp[j] + dp[j - nums[i]]; (遍历背包时从后向前遍历)
完全背包:
dp[i][j] = dp[i - 1][j] + dp[i][j - nums[i - 1]];
dp[j] = dp[j] + dp[j - nums[i]]; (遍历背包时从前向后遍历)
背包中物品的排列数
能放入时组合数等于放入新物品前排列数加 放入新物品后排列数
完全背包:
dp[j] = dp[j] + dp[j - nums[i]](不可使用二维 dp 数组,必须先遍历背包,再遍历物品)
放满背包的最少物品数
初始化时 dp[0][0] = dp[0] = 1,dp[0][1~i] = dp[1 ~i] = max
完全背包:
if (dp[j - i * i] != max),有能满足的组合
dp[i][j] = Math.min(dp[i - 1][j], 1 + dp[i][j - coins[i - 1]]);
dp[j] = Math.min(dp[j], 1 + dp[j - coins[i]]);