目录
一.引言
给定一定日期内的股票价格,求交易 1 次、2 次、K 次、带冷冻期、带交易费等衍生出多道股票买卖的问题,今天通过一个模版搞定所有股票问题。
二.股票问题通式
1.股票买卖分析
股票买卖的本质就是三种状态,买 buy、卖 sell 以及保持 rest:
- rest 情况下,可以保持持有股票、可以保持没有股票
- buy 情况下,必须是在未拥有股票的情况下
- sell 情况下,必须是在拥有股票的情况下
基于天数 i、交易次数 k 以及是否持有股票 [0, 1],我们可以得到总状态数:
2.股票买卖状态转移
基于上面的总状态数,我们可以计算第 i 天第 k 次交易时我们手中的最大收益。
三.股票问题全解
1.股票交易 - 交易 1 次
链接: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
# dp[i] = max(dp[i] - pre_min, dp[i-1])
dp = [0] * len(prices)
pre_min = prices[0]
for i in range(1, len(prices)):
dp[i] = max(prices[i] - pre_min, dp[i-1])
if pre_min > prices[i]:
pre_min = prices[i]
return dp[-1]
2.股票交易 - 交易多次
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
# 天数 i = len(prices) 交易次数 k = 1 状态 0/1 拥有,不拥有
dp = [[0, 0] for _ in range(len(prices))]
# 初始状态
dp[0][0] = 0
dp[0][1] = -1 * prices[0]
# 递推
for i in range(1, len(prices)):
# 不持有 = 保持昨天状态 or 昨天有股票且今天卖了
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
# 持有 = 保持昨天状态 or 昨天没股票且今天买了
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
return max(dp[-1][0], dp[-1][1])
3.股票交易 - 交易 2 次
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
# 天数 i = len(prices) 交易次数 k = 2 状态 0/1 拥有,不拥有
dp = [[0, 0, 0, 0] for _ in range(len(prices))]
# 初始化状态 分别代表第 i 次买、卖股票时手里最大收益 Profit
dp[0][0] = -1 * prices[0]
dp[0][1] = 0
dp[0][2] = -1 * prices[0]
dp[0][3] = 0
for i in range(1, len(prices)):
# 第一次买票 - Rest 或者 买今天更便宜的
dp[i][0] = max(dp[i-1][0], -prices[i])
# 第一次卖票 - Rest 或者 今天把昨天的卖了
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
# 第二次买票 - Rest 或者 上一次卖完再买现在的
dp[i][2] = max(dp[i-1][2], dp[i-1][1] - prices[i])
# 第二次卖票 - Rest 或者 卖了第二次买的
dp[i][3] = max(dp[i-1][3], dp[i-1][2] + prices[i])
return dp[-1][-1]
4.股票交易 - 交易 K 次
链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/
class Solution(object):
def maxProfit(self, k, prices):
"""
:type k: int
:type prices: List[int]
:rtype: int
"""
if len(prices) <= 1:
return 0
k = min(k, len(prices) // 2)
# 天数 i = len(prices) 交易次数 k = 2 状态 0/1 拥有,不拥有
dp = [[0, 0] * k for _ in range(len(prices))]
# 初始化状态 分别代表第 i 次买、卖股票时手里最大收益 Profit
for i in range(len(dp[0])):
dp[0][i] = -1 * prices[0] * ((i + 1) % 2)
for i in range(1, len(prices)):
# 第一次买票 - Rest 或者 买今天更便宜的
dp[i][0] = max(dp[i-1][0], -prices[i])
for j in range(1, len(dp[0])):
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + (-1)** (j+1) * prices[i])
return dp[-1][-1]
交易 K 次我们可以模仿交易 2 次的 4 种状态进行归纳总结。
5.股票交易 - 含冷冻期
class Solution(object):
def maxProfit(self, prices):
"""
0 - 目前有一只股票对应的最大收益
1 - 目前没有股票,处于冷冻期的最大收益
2 - 目前没有股票,不处于冷冻期
"""
N = len(prices)
dp = [[0] * 3 for _ in range(N)]
dp[0][0] = -prices[0]
dp[0][1], dp[0][2] = 0, 0
for i in range(1, N):
# 第 i 天有一只股票: A.还拿着上一天的股票 dp[i-1][0] B.昨天没股票且能买 dp[i-1][2] - price
dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])
# 第 i 天处于冷冻期 A.i-1天卖股票了 说明 i-1 天手里必须有股票 -> dp[i-1][0] + price
dp[i][1] = dp[i-1][0] + prices[i]
# 第 i 天没股票且非冷冻期 A.昨天也没股票也非冷冻 dp[i-1][2] B.昨天没股票冷冻 dp[i-1][1]
dp[i][2] = max(dp[i-1][2], dp[i-1][1])
return max(dp[-1])
6.股票交易 - 含交易费
class Solution(object):
def maxProfit(self, prices, fee):
"""
:type k: int
:type prices: List[int]
:rtype: int
"""
if len(prices) <= 1:
return 0
k = len(prices) // 2
# 天数 i = len(prices) 交易次数 k = 2 状态 0/1 拥有,不拥有
dp = [[0, 0] * k for _ in range(len(prices))]
# 初始化状态 分别代表第 i 次买、卖股票时手里最大收益 Profit
for i in range(len(dp[0])):
dp[0][i] = -1 * (prices[0] + fee) * ((i + 1) % 2)
for i in range(1, len(prices)):
# 第一次买票 - Rest 或者 买今天更便宜的
dp[i][0] = max(dp[i-1][0], -prices[i] - fee)
for j in range(1, len(dp[0])):
action = (-1) ** (j+1)
if action > 0:
profit = prices[i]
else:
profit = -prices[i] - fee
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + profit)
return dp[-1][-1]
上面的方法通式易于理解,但是随着交易次数的增加,DP 状态空间会扩大。
这里还有另一个简单的双指针考虑法,大家可以体会下,本文主要体会上面 DP 转移方程解题的,这里不多赘述:
class Solution:
def maxProfit(self, prices, fee):
buy, sell = -float("inf"), 0
for p in prices:
buy = max(buy, sell - p - fee)
sell = max(sell, buy + p)
return sell
四.总结
这里作为自己的笔记整理了 6 道股票相关的问题,主要是体会 DP 转移方程以及两种状态 [0, 1] 没股票和有股票,在冷冻期一题我们还需增加状态 2 即冷冻期。这里交易 K 次和交易多次,都可以通过交易 2 次的写法推广至 For 循环交易多次。而交易费则只需要在 buy 的时候多减去一个 fee 即可。
Tips:
这里再解释下为什么第一天买股票时 profit 为 - prices[0],因为我们的起始收益为 0,如果我们第一天购买股票,则我们的收益是 0 - prices[0],所以此时的收益为 - prices[0]。而多次交易时多次购买的起始收益都为 - prices[0] 这里可以理解为第一天连续买卖多次,多次买卖结束后收益为 0,因为是一天多次交易。而再下一次购买时,和初始的第一次买是一样的情况,所以初始化收益也是 - prices[0]。