动态规划总结:背包、打家劫舍、买卖股票问题

动态规划

说明: 刷题顺序参考《代码随想录》
动态规划:重点题型 —> 背包、打家劫舍、买卖股票问题

1. 背包问题

情景,背包容量V,物品价值w,体积v

如何使得背包的价值最大,这就是背包问题

1.1 0 - 1背包

每件物品只有一件,你可以选择放或者不放, 也就是 0 或者 1

 dp[i][j]: 可以放0-i物品,背包重量是j,最大的value;
import java.util.Arrays;

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);
    }
    public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){
        int goods = weight.length;  // 获取物品的数量
        int[][] dp = new int[goods + 1][bagSize + 1];  // 给物品增加冗余维,i = 0 表示没有物品可选

        // 初始化dp数组,默认全为0即可
        // 填充dp数组
        for (int i = 1; i <= goods; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j < weight[i - 1]) {  // i - 1 对应物品 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 - 1]] + value[i - 1]);  // i - 1 对应物品 i
                }
            }
        }

        // 打印dp数组
        for(int[] arr : dp){
            System.out.println(Arrays.toString(arr));
        }
    }
}
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); // 数组压缩。为什么数组压缩就一定要使用倒序
// dp[j] 的原理就是复制上一行的,所以,前面的遍历一定不可以改变dp[i] 的值
// 所以一定得倒序
// dp[4],比较的时候会使用到比dp[4]更小的数组,所以dp[0 - 3]就是不能被遍历过
// 因此就需要进行倒序
// 而二维数组就没有这个要求,遍历第二行并不会改变第一行的值

dp[j] : 来自上一行

dp[j - w[i]] + w[i] : 上一行的基础上进行改变

总结:思路和二维是一样的

1.2 完全背包

每件有无限,放几个或者不放

// 递推公式
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);  // 正序就是完全背包问题了
零钱兑换:dp[j] = dp[j] + dp[j - coins[i]]; 
0 && 1: dp[i][j] = max(dp[i][j], dp[i - 0][j - 1] + 1); 
1.3 总结

问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]); ,对应题目如下:

问装满背包有几种方法:dp[j] += dp[j - nums[i]] ,对应题目如下:

问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); ,对应题目如下:

问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]); ,对应题目如下:


2. 打家劫舍

2.1 打家劫舍1
dp[j] : 当前最大;
dp[j] = Math.max(dp[j - 1], dp[j - 2] + nums[j]);
2.2 打家劫舍2
房屋首尾相接;
比较:0 ~ n -21 ~ n - 1;
2.3 打家劫舍3
树形结构, 父子关系被打劫, 就会报警;
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(){}
    TreeNode(int val, TreeNode left, TreeNode right){
        this.val = val;
        this.left = left;
        this.right = right;
    }
}
/*
public class LinkNode{
	int val;
	LinkNode next;
	LinkNode(){}
	LinkNode(int val) {
		this.val = val;
		this.next = null;
	}
	LinkNode(int val, LinkNode next) {
		this.val = val;
		this.next = next;
	}
}
*/
class Solution {
    public int rob(TreeNode root) {
        int[] res = robAction(root);
        return Math.max(res[0], res[1]);
    }
    int[] robAction(TreeNode root) {
        int[] res_root = new int[2]; // 0 - 1
        // 0 : 不偷当前结点
        // 1 : 偷
        if(root == null) return res_root;  // {0, 0}

        int[] res_left = robAction(root.left);
        int[] res_right = robAction(root.right);

        res_root[1] = root.val + res_left[0] + res_right[0]; // 偷root, = root.val + 左(0) + 右(0)
        res_root[0] = Math.max(res_left[0], res_left[1]) + Math.max(res_right[0], res_right[1]);
        // 不偷root, = max(左边) + max(右边)

        return res_root;
    }
}

3. 买卖股票

3.1 买卖股票1
dp[i][0] 表示第i天持有股票所得最多现金;
dp[i][1] 表示第i天不持有股票的现金;
// 递推公式
dp[0][0] = prices[0];
dp[i][0] = max(dp[i - 1][0], -prices[i]); // 只交易一次,持有之前的 && 换新的
dp[i][1] = max(prices[i] + dp[i - 1][0], dp[i - 1][1]);
3.2 买卖股票2 (无限买卖的情况)
dp[i][0] 持有;
dp[i][1] 不持有;
// 递推公式
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 持有,在之前的基础上持有(也就是dp[i - 1][1])
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]); // 卖出
3.3 买卖股票3
// 关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。
/*
第一次持有股票
第一次不持有股票
第二次持有股票
第二次不持有股票
*/
int[][] dp = new int[n][5];
dp[0][1] = -prices[0]; // 第一次持有
dp[0][2] = 0; // 第一次出手
dp[0][3] = -prices[0]; // 第二次持有
dp[0][4] = 0; // 第二次出手

dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
dp[i][2] = Math.max(dp[i - 1][2], prices[i] + dp[i - 1][1]); // 出手基于持有
dp[i][3] = Math.max(dp[i - 1][3], -prices[i] + dp[i - 1][2]); // 出手后再持有
dp[i][4] = Math.max(dp[i - 1][4], prices[i] + dp[i - 1][3]); // 第二次持有的基础上

3.4 买卖股票4
// 1, 3, 4 分别是限制了买卖数量(最多1次,2次,k次 )
// 必须在再次购买前出售掉之前的股票
int[][] dp = new int[n][2 * k + 1]; // 1 - 2 * k
for(int i = 1; i <= k; i ++) {
    dp[0][2 * i - 1] = - prices[0]; // 初始化
}
for(int i = 1; i < n; i++) {
    for(int j = 1; j <= k; j ++) {
        dp[i][2 * j - 1] = Math.max(dp[i - 1][2 * j - 1], dp[i - 1][2 * j - 2] - prices[i]);
        dp[i][2 * j] = Math.max(dp[i - 1][2 * j], prices[i] + dp[i - 1][2 * j - 1]);
    }
}
return dp[n - 1][2 * k];
3.5 最佳买卖股票时机含冷冻期 ( hard )
int[][] dp = new int[prices.length + 1][2];
dp[1][0] = -prices[0];

/*
dp[i][0] 第i天持有股票收益;
dp[i][1] 第i天不持有股票收益;
情况一:第i天是冷静期,不能以dp[i-1][1]购买股票, 所以以dp[i - 2][1]买股票,没问题
情况二:第i天不是冷静期,理论上应该以dp[i-1][1]购买股票,但是第i天不是冷静期说明,第i-1天没有卖出股票,
       则dp[i-1][1]=dp[i-2][1],所以可以用dp[i-2][1]买股票,没问题
*/

dp[i][0] = Math.max(dp[i - 1][0], dp[i - 2][1] - prices[i - 1]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i - 1]);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值