买卖股票Ⅰ
题目描述
解题思路
目标是要求得最大利润,可见包含最优子问题的特点,同时很显而易见,这些子问题是互斥的,所以可以往动态规划方面考虑。
状态转移方程:
可以先列出dp的关系和利润的关系方程,,n表示是第几天,status表示当天的持仓状态,有两种持仓和未持仓。状态转移方程则为:和。
代码实现
自顶向下,暴力递归
from typing import List
# 自顶向下
class Solution:
def maxProfit(self, prices: List[int]) -> int:
def dp(n, status):
# base case
if n == 0:
if status == 0:
return 0
else:
return -prices[0]
if status == 0: # 今日未持有股票
return max(dp(n - 1, 0), dp(n - 1, 1) + prices[n])
# 今日持有股票
return max(dp(n - 1, 1), -prices[n])
return dp(len(prices) - 1, 0)
执行结果:
自顶向下,备忘录优化
from typing import List
# 自顶向下
class Solution:
def maxProfit(self, prices: List[int]) -> int:
memo = [[None] * 2 for _ in range(len(prices))]
memo[0] = 0, -prices[0]
def dp(n, status):
# base case
if memo[n][status] is not None:
return memo[n][status]
else:
if status == 0: # 今日未持有股票
ret = max(dp(n - 1, 0), dp(n - 1, 1) + prices[n])
else:
# 今日持有股票
ret = max(dp(n - 1, 1), -prices[n])
memo[n][status] = ret
return ret
return dp(len(prices) - 1, 0)
执行结果:
可以看到,优化后虽然提交结果垫底,但是已经不超时了。
自底向上,有限迭代
除了从结果(也就是递归树的根节点)出发,我们还可以从base case出发,有限次的迭代求出最终的结果。
代码实现:
from typing import List
# 自底向上
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp = [[0, 0] for _ in range(n)]
dp[0] = [0, -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], -prices[i])
return dp[n - 1][0]
执行结果:
自底向上,空间优化
可以发现每次的运行结果都只跟前一次的有关,dp数组可以简化为两个变量
from typing import List
# 自底向上,空间优化
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp_i_0 = 0
dp_i_1 = -prices[0]
for i in range(1, n):
dp_i_0, dp_i_1 = max(dp_i_0, dp_i_1 + prices[i]), max(dp_i_1, -prices[i])
return dp_i_0
执行结果:
股票买卖Ⅱ
题目描述
简单来说就是在上题的基础上,不限制买卖股票的次数,股票交易次数为无限次。
解题思路
解题思路基本跟上题一致,不同的是状态转移方程中的改为
代码实现
from typing import List
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp_i_0 = 0
dp_i_1 = -prices[0]
for i in range(1, n):
dp_i_0, dp_i_1 = max(dp_i_0, dp_i_1 + prices[i]), max(dp_i_1, dp_i_0-prices[i])
return dp_i_0
提交结果
股票买卖Ⅲ
题目描述
解题思路
本题将买卖次数限定为两次,那么就需要将当天未持仓和当天持仓这两种状态再跟买卖的次数结合起来,所以一共有了四种状态:
- 买过一次,当前为持仓状态
- 卖过一次,当前为未持仓状态
- 买过两次,当前为持仓状态
- 卖过两次,当前为未持仓状态
状态转移方程就变为:
代码实现
from typing import List
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
# 初始状态设置
buy1 = buy2 = -prices[0]
sell1 = sell2 = 0
for i in range(1, n):
buy1 = max(buy1, -prices[i])
sell1 = max(sell1, buy1+prices[i])
buy2 = max(buy2, sell1-prices[i])
sell2 = max(sell2, buy2+prices[i])
return sell2
提交结果:
股票买卖Ⅳ
题目描述
解题思路
此题将最大交易次数作为变量k输入,那么状态转移方程就需要将k也考虑进去:
第
i天,经过了k次交易,当前持仓的收益
第
i天,经过了k次交易,当前未持仓的收益同时需要注意考虑边界条件。
代码实现
from typing import List
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
n = len(prices)
k = min(k, n // 2)
if not prices or k == 0:
return 0
dp = [[[0] * k, [0] * k] for _ in range(n)]
# 初始化,第一天买的都为-prices[0]
dp[0][0] = [-prices[0]] * k
for i in range(1, n):
for j in range(0, k):
# 如果是第一次买直接就是-prices[i]
# buy_j=max(buy_j, sell_j-1 - price)
dp[i][0][j] = max(dp[i - 1][0][j], -prices[i] if j == 0 else dp[i - 1][1][j - 1] - prices[i])
# sell_j=max(sell_j,buy_j + price)
dp[i][1][j] = max(dp[i - 1][1][j], dp[i - 1][0][j] + prices[i])
return dp[n - 1][1][k - 1]
提交结果:
股票买卖(冷冻期)
题目描述
解题思路
有了冷冻期,那么买入时候的最大利润就是跟前两天的卖出利润有关,这里没有对买卖次数做限制,那么可以在股票买卖Ⅱ的基础上修改,状态转移方程就可以更改为:
代码实现
from typing import List
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
if n <= 1:
return 0
dp = [[0, 0] for _ in range(n)]
dp[0] = [0, -prices[0]]
dp[1][1] = max(dp[0][1], dp[0][0] - prices[1])
for i in range(1, n):
# 未持有
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
if i > 1:
# 持有
dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i])
return dp[n - 1][0]
提交结果:
股票买卖(手续费)
题目描述
解题思路
跟股票买卖Ⅱ基本一致,只是将fee也算到成本中去。
代码实现
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp_i_0 = 0
dp_i_1 = -prices[0]
for i in range(1, n):
dp_i_0, dp_i_1 = max(dp_i_0, dp_i_1 + prices[i]-fee), max(dp_i_1, dp_i_0 - prices[i])
return dp_i_0
提交结果: