class Solution {
public int fib(int n) {
if (n <= 1) return n;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int index = 2; index <= n; index++){
dp[index] = dp[index - 1] + dp[index - 2];
}
return dp[n];
}
}
1. 确定dp[i]的含义:第i个斐波那契数
2. 递推公式:dp[i] = dp[i-1] + dp[i-2]
3. dp数组如何初始化:dp[0] = 1, dp[1] = 1
4. 确定遍历顺序:从前往后遍历
5. 打印dp数组:用来debug
// 常规方式
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
1. 确定dp[i]的含义:达到第i阶,有dp[i]种方法
2. 递推公式:dp[i] = dp[i-1] + dp[i-2]
3. dp数组如何初始化:dp[1] = 1, dp[2] = 2
4. 确定遍历顺序:从前往后遍历
5. 打印dp数组:用来debug
class Solution {
public int minCostClimbingStairs(int[] cost) {
int len = cost.length;
int[] dp = new int[len + 1];
// 从下标为 0 或下标为 1 的台阶开始,因此支付费用为0
dp[0] = 0;
dp[1] = 0;
// 计算到达每一层台阶的最小费用
for (int i = 2; i <= len; i++) {
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[len];
}
}
1. 确定dp[i]的含义:达到第i层,需要的最小花费是dp[i]
2. 递推公式:dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
可以由i-1的位置跳了一步和i-2的位置跳了两步到了i的位置,然后再加上跳i-1或者跳i-2本身的花费就得到了dp[i] 然后再选择最少的花费
3. 初始化dp[0] = 0, dp[1] = 0 最开始站在0或者1都不需要花费体力,所以都是初始化为0
4. 确定遍历顺序:从前往后遍历
class Solution{
public static int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
//初始化
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
1. 确定dp[i][j]的含义:从(0,0)走到(i,j)有多少条路径
2. 递推公式:
dp[i-1][j] 走一步到dp[i][j]
dp[i][j-1]走一步到dp[i][j]
所以 dp[i][j] = dp[i-1][j] + dp[i][j-1]
3. 初始化:分别需要在两个方向上进行初始化
dp[i][0] = 1
dp[0][j] = 1
4. 遍历顺序:从左向右,从上往下遍历
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
//如果在起点或终点出现了障碍,直接返回0
if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) {
return 0;
}
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
dp[i][0] = 1;
}
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
dp[0][j] = 1;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = (obstacleGrid[i][j] == 0) ? dp[i - 1][j] + dp[i][j - 1] : 0;
}
}
return dp[m - 1][n - 1];
}
}
1. 确定dp[i][j]的含义:从(0,0)走到(i,j)有多少种不同的路径
2. 递推公式:
dp[i-1][j] 走一步到dp[i][j]
dp[i][j-1]走一步到dp[i][j]
所以 if obs[i][j] == 0 (没有障碍物的情况下)
dp[i][j] = dp[i-1][j] + dp[i][j-1]
3. 初始化:分别需要在两个方向上进行初始化
dp[i][0] = 1
dp[0][j] = 1
一旦遇到障碍物,for循环就终止
for (int i = 0; i < m && obs[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obs[0][j] == 0; j++) dp[0][j] = 1;
4. 遍历顺序:从左向右,从上往下遍历
class Solution {
public int integerBreak(int n) {
//dp[i] 为正整数 i 拆分后的结果的最大乘积
int[] dp = new int[n+1];
dp[2] = 1;
for(int i = 3; i <= n; i++) {
for(int j = 1; j <= i-j; j++) {
// 这里的 j 其实最大值为 i-j,再大只不过是重复而已,
//并且,在本题中,我们分析 dp[0], dp[1]都是无意义的,
//j 最大到 i-j,就不会用到 dp[0]与dp[1]
dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
// j * (i - j) 是单纯的把整数 i 拆分为两个数 也就是 i,i-j ,再相乘
//而j * dp[i - j]是将 i 拆分成两个以及两个以上的个数,再相乘。
}
}
return dp[n];
}
}
1. dp数组含义:对i进行拆分,最大的乘积为dp[i]
2. 递推公式:
j * (i-j): 是把i拆成两个数
j * dp[i-j]:关于为什么j*dp【i-j】不用考虑拆 j
3. 初始化:dp[0] = 0, dp[1] = 0, dp[2] = 1
class Solution {
public int numTrees(int n) {
//初始化 dp 数组
int[] dp = new int[n + 1];
//初始化0个节点和1个节点的情况
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
//对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
//一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
}
1.dp数组的含义:输入i,输出dp[i]种不同的二叉搜索树
2. 递推公式:dp[i] += dp[j-1] * dp[i-j]
dp[i] = 所有左子树的可能 * 所有右子树的可能
左子树节点数量 + 右子树节点数量 + 头节点数量(1) = i(总数量)
(j - 1) + (i - j) + 1 = i
因为是二叉搜索树 所以以j来遍历 左边有j-1个节点 右边有i-j个节点 因为左边所有的节点都会比j小 而右边所有的节点都会比j大
dp[3] = dp[0] * dp[2] + dp[1] * dp[1] + dp[2] * dp[0]
3. 初始化:dp[0] = 0, dp[1] = 1
4. 遍历顺序:从小到大
0-1背包问题理论基础
public class BagProblem {
public static void main(String[] args) {
int[] weight = {1,3,4};
int[] value = {15,20,30};
int bagSize = 4;
testWeightBagProblem(weight,value,bagSize);
}
/**
* 动态规划获得结果
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){
// 创建dp数组
int goods = weight.length; // 获取物品的数量
int[][] dp = new int[goods][bagSize + 1];
// 初始化dp数组
// 创建数组后,其中默认的值就是0
for (int j = weight[0]; j <= bagSize; j++) {
dp[0][j] = value[0];
}
// 填充dp数组
for (int i = 1; i < weight.length; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j < weight[i]) {
/**
* 当前背包的容量都没有当前物品i大的时候,是不放物品i的
* 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
*/
dp[i][j] = dp[i-1][j];
} else {
/**
* 当前背包的容量可以放下物品i
* 那么此时分两种情况:
* 1、不放物品i
* 2、放物品i
* 比较这两种情况下,哪种背包中物品的最大价值最大
*/
dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]);
}
}
}
// 打印dp数组
for (int i = 0; i < goods; i++) {
for (int j = 0; j <= bagSize; j++) {
System.out.print(dp[i][j] + "\t");
}
System.out.println("\n");
}
}
}
1. dp数组的定义:
dp[i][j]: 从[0,i]中任意取物品 放进重量为j的背包 价值总和最大为多少
不放物品i:dp[i-1][j]
放物品i:dp[i-1][j-weight[i]] + value[i]
2. dp公式
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]]
j - weight[i]可以理解为要为物品i留出空间来放i
3. 初始化:
从公式可以看出,i是由i-1推倒出来的
第一列初始化为0,因为背包容量为0。
第一行初始化为value[0],因为只放物品0,所以是物品0的value[0]
4.遍历顺序:
无论是先遍历背包还是先遍历物品,dp[i][j]都是由它正上方的值和左上方的值推导出来的,所以两种遍历顺序都可以
背包问题理论基础2
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWight = 4;
testWeightBagProblem(weight, value, bagWight);
}
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 0; i < wLen; i++){
for (int j = bagWeight; j >= weight[i]; j--){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//打印dp数组
for (int j = 0; j <= bagWeight; j++){
System.out.print(dp[j] + " ");
}
}
1. dp数组含义:
dp[j]:容量为j的背包的最大价值为dp[j]
2. 递推公式:
dp[j] = max(dp[j], dp[j - weight[i] + value[i]])
3.初始化:
dp[0] = 0;
4.遍历顺序:只能是先遍历物品再遍历背包
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int n = nums.length;
int sum = 0;
for(int num : nums) {
sum += num;
}
//总和为奇数,不能平分
if(sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < n; i++) {
for(int j = target; j >= nums[i]; j--) {
//物品 i 的重量是 nums[i],其价值也是 nums[i]
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
//剪枝一下,每一次完成內層的for-loop,立即檢查是否dp[target] == target,優化時間複雜度(26ms -> 20ms)
if(dp[target] == target)
return true;
}
return dp[target] == target;
}
}
1.dp数组含义:容量为j的背包 最大价值为dp[j]
target = sum/2
dp[target] == target
2. 递推公式:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
因为这道题的重量和价值都是相同的,所以公式为dp[j] = max(dp[j], dp[j - nums[i] ]+ nums[i])
3. 初始化
dp[0] = 0
4. 遍历顺序:先遍历物品再遍历背包
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for (int i : stones) {
sum += i;
}
int target = sum >> 1;
//初始化dp数组
int[] dp = new int[target + 1];
for (int i = 0; i < stones.length; i++) {
//采用倒序
for (int j = target; j >= stones[i]; j--) {
//两种情况,要么放,要么不放
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - 2 * dp[target];
}
}
1.dp数组含义:容量为j的背包 最大价值为dp[j]
2. 递推公式:因为这道题的重量和价值都是相同的,所以公式为dp[j] = max(dp[j], dp[j - stone[i] ]+ stone[i])
max(不放石头i,放石头i)
3. 初始化
dp[0] = 0
尽量初始化为最小的非负数,以防止覆盖
dp[3000/2 + 1] = dp[1501] =0
4. 先遍历物品再遍历背包,遍历背包时要从大到小遍历,因为每个物品只能使用一次
(sum - dp[target]) - dp[target]
1.dp数组的含义:满容为j,有dp[j]种方法
2.