算法训练营(day48-51)
算法训练营(day48-51)
动态规划理论基础
动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
举个例子:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
动态规划中 dp[j]
是由 dp[j-weight[i]]
推导出来的,然后取 max(dp[j], dp[j - weight[i]] + value[i])
。
动态规划的解题步骤
- 确定
dp
数组(dp table)以及下标的含义 - 确定递推公式
dp
数组如何初始化- 确定遍历顺序
- 举例推导
dp
数组
121. 买卖股票的最佳时机
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[i]:第i天持有股票所得最多现金
- 确定递推公式
如果第
i
天 持有股票 即dp[i][0]
, 那么可以由两个状态推出来第
i-1
天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
第
i
天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
那么 dp[i][0]
应该选所得现金最大的,所以 dp[i][0] = max(dp[i - 1][0], -prices[i])
;
如果第
i
天 不持有股票 即dp[i][1]
, 也可以由两个状态推出来第
i-1
天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
第
i
天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]
同样 dp[i][1]
取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0])
;
- dp数组的初始化
递推公式的基础就是dp[0] 和 dp[1]
1 | int[] dp = new int[2]; |
- 确定遍历顺序
dp[i]
是根据 dp[i - 1]
推导出来的,那么一定是从前到后遍历!
1 | for(int i = 1; i <= prices.length; i++){ |
- 推导dp数组
详细代码
动态规划:
1 | class Solution { |
贪心:
1 | class Solution { |
122. 买卖股票的最佳时机 II
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[i]:第i天持有股票所得最多现金
- 确定递推公式
如果第
i
天持有股票即dp[i][0]
, 那么可以由两个状态推出来第
i-1
天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
第
i
天买入股票,所得现金就是买入今天的股票后所得现金即:dp[i - 1][1] - prices[i]
那么 dp[i][0]
应该选所得现金最大的,所以 dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] -prices[i])
;
如果第
i
天不持有股票即dp[i][1]
, 也可以由两个状态推出来第
i-1
天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
第
i
天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]
同样 dp[i][1]
取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0])
;
- dp数组的初始化
递推公式的基础就是dp[0] 和 dp[1]
1 | int[][] dp = new int[prices.length][2]; |
- 确定遍历顺序
dp[i]
是根据 dp[i - 1]
推导出来的,那么一定是从前到后遍历!
1 | for(int i = 1; i < prices.length; i++){ |
- 推导dp数组
详细代码
动态规划:
1 | class Solution { |
递归:
1 | class Solution { |
123. 买卖股票的最佳时机 III
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/description/
给定一个数组,它的第 i
个元素是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[i]:第i次买卖股票所得最多现金
- 确定递推公式
如果只有1次 买卖股票 即只存在
dp[0]
和dp[1]
, 那么可以由两个状态推出来dp[0]
表示买入股票:前一天买入,
-price[i - 1]
不做任何操作,保留
dp[0]
dp[1]
表示卖出股票:- 前一天卖出,
dp[0] + price[i - 1]
- 不做任何操作,保留
dp[1]
- 前一天卖出,
如果存在 2次买卖股票 即存在四种情况
dp[0]、dp[1]、dp[2] 和 dp[3]
, 也可以由 一次买卖股票再推出 两个状态:dp[2]
表示 第二次 买入股票:前一天买入,
dp[1] - price[i - 1]
不做任何操作,保留
dp[2]
dp[3]
表示 第二次 卖出股票:- 前一天卖出
dp[2] + price[i - 1]
- 不做任何操作,保留
dp[3]
- 前一天卖出
- dp数组的初始化
递推公式的基础就是四个状态:dp[0] 、 dp[1]、dp[2] 和 dp[3]
1 | int[] dp = new int[4]; |
- 确定遍历顺序
dp[i]
是根据 dp[i - 1]
推导出来的,那么一定是从前到后遍历!
1 | for(int i = 1; i <= prices.length; i++){ |
- 推导dp数组
详细代码
动态规划:
1 | class Solution { |
188. 买卖股票的最佳时机 IV
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/
给定一个整数数组 prices
,它的第 i
个元素 prices[i]
是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[2i]:第k(k = 2i)次买卖股票所得最多现金
确定递推公式
如果 k < 1,直接返回0就行
第1次 买卖股票 即只存在
dp[0]
和dp[1]
, 那么可以由两个状态推出来dp[0]
表示买入股票:前一天买入,
-price[i - 1]
不做任何操作,保留
dp[0]
dp[1]
表示卖出股票:- 前一天卖出,
dp[0] + price[i - 1]
- 不做任何操作,保留
dp[1]
- 前一天卖出,
后面的k次买卖股票,都由 一次买卖股票再推出 两个状态:
for(int j = 2; j < dp.length; j += 2){}
dp[j]
表示买入股票- 前一天买入,
dp[j - 1] - price[i - 1]
- 不做任何操作,保留
dp[j]
- 前一天买入,
dp[j + 1]
表示卖出股票:- 前一天卖出,
dp[j] + price[i - 1]
- 不做任何操作,保留
dp[j + 1]
- 前一天卖出,
- dp数组的初始化
递推公式的基础就是dp[0] 和 dp[1]
1 | int[] dp = new int[2 * k]; |
- 确定遍历顺序
dp[i]
是根据 dp[i - 1]
推导出来的,那么一定是从前到后遍历!
1 | for(int i = 1; i <= prices.length; i++){ |
- 推导dp数组
详细代码
动态规划:
1 | class Solution { |
309. 最佳买卖股票时机含冷冻期
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/
给定一个整数数组prices
,其中第 prices[i]
表示第 *i*
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[i][j]
:第i天状态为j,持有股票所得最多现金
具体可以区分出如下四个状态:
- 不持有股票状态
- 状态一:今天是冷冻期
- 状态二:过了冷冻期
- 状态三:今天卖出股票(明天是冷冻期)
- 持有股票状态
- 状态四:保持买入股票的状态
- 今天买入股票
- 保持股票买入状态
- 状态四:保持买入股票的状态
其中,状态二和状态四可以是同一天,所以主要是三种情况
- 确定递推公式
不持有股票 即
dp[i][0]
, 那么可以由两个状态推出来第
i-1
天就不持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
第
i-1
天卖出股票,所得现金就是卖出今天的股票后所得现金即:dp[i - 1][1] + prices[i]
那么 dp[i][0]
应该选所得现金最大的,所以 dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
;
持有股票 即
dp[i][1]
, 也可以由两个状态推出来第
i-1
天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][1]
第
i-2
天卖出了股票(第i - 1
天是冷冻期),第i
天要买入股票,所得现金就是按照第i -2
天股票价格卖出后所得现金 减去当天的股票金额即:dp[i - 2][0] - prices[i]
同样 dp[i][1]
取最大的,**dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i])
**;
- dp数组的初始化
**递推公式的基础就是第一次买卖股票 dp[0][0]
、dp[0][1]
和 第一次过冷冻期后的 买卖股票 dp[1][0]
、dp[1][1]
**
1 | int[][] dp = new int[prices.length][2]; |
- 确定遍历顺序
dp[i]
是根据 dp[i - 1]
推导出来的,那么一定是从前到后遍历!
1 | for(int i = 2; i < prices.length; i++){ |
- 推导dp数组
详细代码
解法一:
1 | class Solution { |
解法二:
1 | class Solution { |
714. 买卖股票的最佳时机含手续费
题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/
给定一个整数数组 prices
,其中 prices[i]
表示第 i
天的股票价格 ;整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[i]:第i天持有股票所得最多现金
- 确定递推公式
如果第
i
天不持有股票即dp[i][0]
, 那么可以由两个状态推出来第
i-1
天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][0]
第
i
天卖出股票,所得现金就是卖出今天的股票后所得现金即:dp[i - 1][1] + prices[i]
那么 dp[i][0]
应该选所得现金最大的,所以 dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
;
如果第
i
天持有股票即dp[i][1]
, 也可以由两个状态推出来第
i-1
天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][1]
第
i
天买入股票,所得现金就是按照今天股票价格买入后再减去手续费后所得现金即:dp[i - 1][0] - prices[i] - fee
同样 dp[i][1]
取最大的,**dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee)
**;
- dp数组的初始化
递推公式的基础就是dp[0] 和 dp[1]
1 | int[][] dp = new int[prices.length][2]; |
- 确定遍历顺序
dp[i]
是根据 dp[i - 1]
推导出来的,那么一定是从前到后遍历!
1 | for(int i = 1; i < prices.length; i++){ |
- 推导dp数组
详细代码
动态规划:
1 | class Solution { |
递归:
1 | class Solution { |