文章目录
- 前言
- 一、打家劫舍 III(力扣337)【较难】
- 二、买卖股票的最佳时机(力扣121)
- 三、买卖股票的最佳时机II(力扣122)
前言
1、打家劫舍 III
2、买卖股票的最佳时机
3、买卖股票的最佳时机II
一、打家劫舍 III(力扣337)【较难】
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
分析:
树形dp的入门题目
后序遍历
通过递归左节点,得到左节点偷与不偷的金钱。
通过递归右节点,得到右节点偷与不偷的金钱
如何去描述每一个结点的状态?对于每一个结点都有偷和不偷两种状态,所以定义一个一维的dp数组,长度为2.dp[0]不偷当前结点所获得的最大金钱 dp[1]偷当前结点所获得的最大金钱。递归遍历的时候栈会自动保存每一层的dp状态
- 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0](不偷左孩子的最大钱币) + right[0](不偷右孩子的最大金币);
- 如果不偷当前节点,那么左右孩子就可以偷,
- 至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) 【左孩子偷还是不偷】+ max(right[0], right[1])【右孩子偷还是不偷】;
- 最后val1(偷)和val2(不偷) 的最大值返回
递归三部曲:
1、确定参数和返回值
返回值:一个一维的长度为2的dp数组,
参数:传进去根节点
2、终止条件
当前遍历的结点是NULL的话 return【0,0】(偷还是不偷都是0)
3、遍历顺序
后序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int rob(TreeNode root) {
int[] res = robAction1(root);
return Math.max(res[0],res[1]);
}
int[] robAction1(TreeNode root){
int dp[] = new int[2];
if(root==null)
return dp;
int[] left = robAction1(root.left);
int[] right = robAction1(root.right);
//根结点不偷:
dp[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
//根节点偷:
dp[1] = root.val + left[0] + right[0];
return dp;
}
}
二、买卖股票的最佳时机(力扣121)
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
分析:
只能买卖一次
每一天的股票都有两个状态所以dp数组需要一个二维数组。
dp[i][0]:持有这只股票所得到的最大金额
dp[i][1]:不持有这只股票所得到的最大金额
不持有未必当天卖出,但是包含了当天卖出的状态,持有不代表是第i天买入,有可能是之前买入,第i天保持买入的状态
前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
动规五部曲
1、确定dp数组以及下标含义
dp[i][0]:持有这只股票所得到的最大金额
dp[i][1]:不持有这只股票所得到的最大金额
2、递推公式
dp[i][0]: 如果dp[i][0]==dp[i-1][0]说明第i-1天就已经持有这只股票的
如果是第i天才买入这只股票 就需要把对应的钱花出去 -price[i];
因此dp[i][0] = Math.max(dp[i-1][0],-price[i])
同理:dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]+price[i])
前一天保持着不持有股票的这个状态
在第i天决定卖出之后 那么第i-1天保持的是持有状态。通过dp[i-1][0]+price[i]
3、初始化
dp[0][0] = -pricec[0];
dp[0][1] = 0;
4、遍历顺序
从前往后
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i=1;i<prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.length-1][1];
}
}
优化后(滚动数组):
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[2];
dp[0] = -prices[0];
dp[1] = 0;
for (int i = 1; i <= prices.length; i++) {
dp[0] = Math.max(dp[0], -prices[i - 1]);
dp[1] = Math.max(dp[1], dp[0] + prices[i - 1]);
}
return dp[1];
}
}
三、买卖股票的最佳时机II(力扣122)
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
比起买卖股票的最佳时机 区别在于可以多买多卖
贪心算法求解:
class Solution {
public int maxProfit(int[] prices) {
int res =0;
int count =0;
for(int i=1;i<prices.length;i++){
count = prices[i]-prices[i-1];
if(count>0){
res +=count;
}
}
return res;
}
}
动态规划:
dp[1] 没有变化
关键在于dp[0] 因为在买卖多次后 手头的钱会发生一些变化,而不是之前的0-prices[i]
可能是之前买卖多次所获得的利润
因此dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-price[i])
同理:dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0]+price[i])
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i=1;i<prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.length-1][1];
}
}
滚动数组版本
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[2];
dp[0] = -prices[0];
dp[1] = 0;
for(int i=1;i<prices.length;i++){
dp[0] = Math.max(dp[0],dp[1]-prices[i]);
dp[1] = Math.max(dp[1],dp[0]+prices[i]);
}
return dp[1];
}
}