[Leetcode] 买卖股票合集(动态规划)
写完这套题,再搞一台时光机,财务自由不是梦(Doge)
==================================
相关题目链接
121 买卖股票的最佳时机
122 买卖股票的最佳时机 II
123 买卖股票的最佳时机 III
188 买卖股票的最佳时机 IV
309 买卖股票的最佳时机含冷冻期
714 买卖股票的最佳时机含手续费
- 买卖股票的最佳时机(仅一次交易)
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
分析
遍历每一天。用变量 minprice 记录已遍历天数的最小股价,初始值为第一天股价;
同时记录每天可以得到的最大利润 maxprofit,即前一天的最大利润 和(今日股价 - 历史最小值 minprice)的较大者,初始值为0。返回最后一天的maxprofit。
说明:下述动态规划只保存了全区间极值,没有用列表保存每天的极值
class Solution:
def maxProfit(self, prices: List[int]) -> int:
minprice = prices[0]
maxprofit = 0
for price in prices:
minprice = min(minprice,price)
maxprofit = max(maxprofit,price-minprice)
return maxprofit
2. 买卖股票的最佳时机(可多次交易)
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。返回你能获得的最大利润 。
示例 1:
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。总利润为 4 + 3 = 7 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 总利润为 4 。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
方法一:所有上坡阶段求和
标志 flag 记录是否持有股票;
buy 和 sale 记录当前持有股票的交易价格,如果当前不持有股票则为0
遍历每日股价,每次买入股票后,更新buy、sale(持续更新)和flag ;
每次卖出股票后,更新buy、sale 和 flag(均为0);
最后一天需要检查 是否持有股票,持有需卖出
flag | ACTION | flag 更新 | buy更新 | sale更新 | |
prices[i+1] > prices[i] | 0 | 买入 | 1 | prices[i] | prices[i+1] |
prices[i+1] <= prices[i] | 1 | 卖出 | 0 | 0 | 0 |
prices[i+1] > prices[i] | 1 | — | — | — | prices[i+1] |
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# flag 标记是否持有股票
flag = 0
# buy 记录当前持有股票的买入价格
buy = 0
# sale 记录当前股票价格,作为暂时的卖出价格
sale = 0
sum = 0
for i in range(len(prices) - 1):
#如果当前不持有股票,次日股价比今日高,买入股票(更新买入价格),记录次日股价,更新flag标志 持有
if prices[i+1] > prices[i] and not flag:
buy = prices[i]
sale = prices[i+1]
flag = 1
#持有股票 且 次日股价不高于今日股价 --> 卖出股票,清空股票买卖价格,更新flag标志 不持有
elif prices[i+1] <= prices[i] and flag:
sum = sum + (sale - buy)
buy = 0
sale = 0
flag = 0
#持有股票 且 次日股价高于今日股价 --> 继续持有股票,更新卖出价格
elif prices[i+1] > prices[i] and flag:
sale = prices[i+1]
if flag == 1:
sum = sum + (sale - buy)
return sum
方法二:贪心
每天都买卖股票(连续持有可以看作当天卖出后再买入),卖出如果收益为正就加入总和
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
sum = 0
for i in range(1,n):
tmp = prices[i] - prices[i-1]
if tmp > 0:
sum += tmp
return sum
方法三:动态规划
每天有两种状态,收盘后持有或不持有股票;
初始化一个 n x 2的二维数组 dp,求第 0~ n-1 天 持有 和 不持有股票的最大收入;
第n-1 天如果持有股票,卖出一定比继续持有收益大,所以最后返回第n-1天不持有股票的最大收入
dp[i][0] 代表第i天收盘后不持有股票的最大收益 --> 前日不持有今日无操作、前日持有今日卖出
dp[i][1] 代表第i天收盘后持有股票的最大收益 --> 前日已持有今日无操作 、前日不持有今日买入
初始状态,第一天不持有股票最大收益为0;持有即买入一股,最大收益为 -prices[0]
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp= [[0,0]] * n
dp[0][0] = 0
dp[0][1] = -prices[0]
for i in range(1,n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
return dp[n-1][0]
3. 买卖股票的最佳时机含手续费
给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
示例 1:
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8
示例 2:
输入:prices = [1,3,7,5,10,3], fee = 3
输出:6
使用动态规划,和上述题2 一样,多了一个手续费,买入时算在开销里
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp= [[0,0]] * n
dp[0][0] = 0
dp[0][1] = -prices[0]-fee
for i in range(1,n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]-fee)
return dp[n-1][0]
4. 买卖股票的最佳时机含冷冻期
给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
示例 2:
输入: prices = [1]
输出: 0
动态规划分析
每天有三种状态,收盘后股票持有、不持有(在冷冻期)、不持有(不在冷冻期)
初始化一个 n x 3的二维数组 dp,求第 0~ n-1 天 上述三种状态的最大收入;
说明:如果第i天有卖出,则收盘后进入冷冻期,第i+1天不能买入,我们把这个冷冻期的状态记在第i天
dp[i][0] 代表第i天收盘后不持有股票、且不在冷冻期的最大收益
收盘后不在冷冻期 ==> 今日没有卖出股票
+收盘后不持有股票 ==> 前日就不持有股票(前日在 或 不在冷冻期)
状态转移方程:dp[i][0] = max(dp[i-1][0], dp[i-1][1])
dp[i][1] 代表第i天收盘后不持有股票、且在冷冻期的最大收益
收盘后在冷冻期 ==> 今日卖出股票
+收盘后不持有股票 ==> 昨日持有股票 且 今日卖出股票
状态转移方程:dp[i][1] = dp[i-1][2] + prices[i]
dp[i][2] 代表第i天收盘时持有股票的最大收益
收盘时持有股票 ==> 昨日已经持有股票 或 昨日不持有不在冷冻期、今日买入
状态转移方程:dp[i][2] = max(dp[i-1][2], dp[i-1][0] - prices[i])
初始状态:
dp[0][0] = 0 #第一天不持有股票最大收益为0
dp[0][1] = 0 #第一天不持有股票最大收益为0
dp[0][2] = -prices[0] #第一天持有即买入一股,最大收益为 -prices[0]
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
if n == 1:
return 0
#dp= [[0,0,0]] * n
dp= [[0] * 3 for _ in range(n)]
#dp[0][0] = 0
#dp[0][1] = 0
dp[0][2] = -prices[0]
# 1+ (n-1)行
#dp = [[0, 0, -prices[0]]] + [[0] * 3 for _ in range(n - 1)]
for i in range(1,n):
#不持有股票,且不在冷冻期的最大收益
dp[i][0] = max(dp[i-1][0], dp[i-1][1])
#不持有股票,且在冷冻期的最大收益
dp[i][1] = dp[i-1][2] + prices[i]
#持有股票的最大收益
dp[i][2] = max(dp[i-1][2], dp[i-1][0] - prices[i])
return max(dp[n-1][0],dp[n-1][1])
5. 买卖股票的最佳时机(最多可以完成两笔交易)
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
示例 4:
输入:prices = [1]
输出:0
方法一:动态规划
每天有五种状态,初始化一个 n x 5的二维数组 dp,求第 0~ n-1 天下述五种状态的最大收入
dp[i][0] 收盘后不持有股票,未交易过的最大收益 --> 0
dp[i][1] 收盘后是首次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为首次买入
dp[i][2] 收盘后不持有股票,已交易过一次的最大收益 --> 昨天同状态,或前序状态 + 今日为首次卖出
dp[i][3] 收盘后是第二次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为二次买入
dp[i][4] 收盘后不持有股票,已交易过两次的最大收益 --> 昨天同状态,或前序状态 + 今日为二次卖出
初始状态:
dp[0][0],dp[0][2],dp[0][4] 归为第一天不持有股票的情况,最大收益为0
dp[0][1],dp[0][3] 归为第一天持有股票的情况,最大收益为 -prices[0]
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp = [[0]*5 for _ in range(n)]
#dp[i][0] 不持有股票,未交易过的最大收益
#dp[i][1] 首次持有股票状态的最大收益
#dp[i][2] 不持有股票,已交易过一次的最大收益
#dp[i][3] 第二次持有股票状态的最大收益
#dp[i][4] 不持有股票,已交易过两次的最大收益
dp[0][1] = -prices[0]
dp[0][3] = -prices[0]
if n == 1:
return 0
for i in range(1,n):
dp[i][0] = 0
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i])
dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i])
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i])
return max(dp[n-1][0], dp[n-1][2], dp[n-1][4])
方法二:遍历的同时分为两个区间动态规划(运行超时)
将prices分为两个区间,各自找到两个区间一次交易的最大值(使用上述题1的动态规划方法),相加即为当前区间分段方法的最大收益。遍历每种区间分段的最大值,但是这样提交超出了时间限制。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
maxProfit = 0
# i 为分割点,在0~n-1范围遍历
for i in range(n):
maxProfit1, maxProfit2 = 0, 0
#左区间右区间各自遍历求最大收益
#如果i=0,只执行右区间的遍历;如果i=n-1,只执行左区间的遍历,这两种情况都对应只交易一次
if i > 0:
minPrice1 = 10**6
for j in range(i):
minPrice1 = min(minPrice1,prices[j])
maxProfit1 = max(maxProfit1 ,prices[j] - minPrice1)
if i < n-1:
minPrice2 = 10**6
for k in range(i,n):
minPrice2 = min(minPrice2,prices[k])
maxProfit2 = max(maxProfit2 ,prices[k] - minPrice2)
maxProfit = max(maxProfit, maxProfit1 + maxProfit2)
return maxProfit
方法三:从两端各自动态规划求最大收益
从prices左侧开始动态规划找每日收益最大值
从prices右侧开始动态规划找每日收益最小值(高买低卖,反过来就是低买高卖)
得到两个列表,前者减后者就是按天的最大值列表,该列表最大值即为所求
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp = [0] * n
dpR = [0] * n
if n == 1:
return 0
dp[0] = 0
minVal = prices[0]
for i in range(n):
minVal = min(minVal, prices[i])
dp[i] = max(dp[i-1],prices[i]-minVal)
dpR[n-1] = 0
maxVal = prices[-1]
for j in list(range(1,n))[::-1]:
maxVal = max(maxVal,prices[j])
dpR[j-1] = min(dpR[j],prices[j-1]-maxVal)
maxProfit = 0
for i in range(n):
maxProfit = max(maxProfit,dp[i] - dpR[i])
return maxProfit
6. 买卖股票的最佳时机(最多可以完成k笔交易)
给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
示例 2:
输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。 随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
其实就是将上述题5一般化,使用动态规划
两笔交易将prices分为五个区间 --> k笔交易将prices分为2k+1个区间,每天有2k+1种状态
初始化一个 n x (2k+1) 的二维数组 dp,求第 0~ n-1 天下述2k+1种状态的最大收入
dp[i][0] 收盘后不持有股票,未交易过的最大收益 --> 0
dp[i][1] 收盘后是首次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为首次买入
dp[i][2] 收盘后不持有股票,已交易过一次的最大收益 --> 昨天同状态,或前序状态 + 今日为首次卖出
……
dp[i][2*k-1] 收盘后是第二次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为第k次买入
dp[i][2*k] 收盘后不持有股票,已交易过两次的最大收益 --> 昨天同状态,或前序状态 + 今日为k次卖出
状态转移方程一般化:
j为奇数,收盘后持有 --> 昨日持有 或 昨日不持有、今日买入
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] - prices[i])
j为偶数,收盘后不持有 --> 昨日不持有 或 昨日持有、今日卖出
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + prices[i])
初始状态:
dp[0][0],dp[0][2] ... dp[0][2*k] 归为第一天不持有股票的情况,最大收益为0
dp[0][1],dp[0][3]... dp[0][2*k-1] 归为第一天持有股票的情况,最大收益为 -prices[0]
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
n = len(prices)
if n == 1:
return 0
dpLine0 = [ -prices[0] if x%2 == 1 else 0 for x in range(2*k+1) ]
dp = [dpLine0] + [[0]*(2*k+1) for _ in range(n-1)]
#dp[i][0] 不持有股票,未交易过的最大收益
#dp[i][1] 首次持有股票状态的最大收益
#dp[i][2] 不持有股票,已交易过一次的最大收益
#dp[i][3] 第二次持有股票状态的最大收益
#dp[i][4] 不持有股票,已交易过两次的最大收益
#...
for i in range(1,n):
dp[i][0] = 0
for j in range(1,2*k+1):
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] - prices[i]) if j%2 == 1 else max(dp[i-1][j], dp[i-1][j-1] + prices[i])
return dp[n-1][2*k]
#return max(dp[n-1])