LeetCode:121. 买卖股票的最佳时机(python)
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路1:
- 从后往前遍历,通过已找到的最高股票价格更新最大利润值,随后查看是否需要更新最高股票价格,若是,则更新。
附代码1(Python):
class Solution:
def maxProfit(self, prices):
if not prices:
return 0
res = 0 # 初始化最大利润
max_p = prices[-1] # 初始化最大股票价格
for i in range(len(prices)-2, -1, -1): # 从后往前遍历
res = max(res, max_p-prices[i]) # 更新最大利润
max_p = max(max_p, prices[i]) # 更新最高股票价格
return res
test = Solution()
prices_li = [[7,1,5,3,6,4],
[7,6,4,3,1]
]
for prices in prices_li:
print(test.maxProfit(prices))
5
0
思路2:动态规划
1. 分析股票交易动态规划方案
-
状态:
- 天数
- 交易次数
- 持有状态(1表示持有,0表示未持有)
用
dp[i][k][0 或 1]
表示第i
天,第k
次交易,持有或未持有的最大收益,最多有n*K*2
中状态,n
为总天数,K
为最多交易次数。 -
选择:
- 买入,buy
- 卖出,sell
- 无操作,rest
在
dp[i][k][0]
时为未持有状态,则有两种选择max(无操作,买入)
;在dp[i][k][1]
时为持有状态,则有两种选择max(无操作,卖出)
。 -
状态转移:
得状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max( 选择 rest , 选择 sell )
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 选择 rest , 选择 buy )
注意:buy 或 sell 之后交易次数 +1 (此处选择在 buy 之后记录交易次数)
-
初始化:第
i
天的最大收益需要由第i-1
天的最大收益转移-
若
dp
从i=0
开始,则需要考虑i=-1
的情况,第 0 天有两种选择,rest(无操作)或 buy(买入)。状态为 0 (未持有股票)时只能选择 rest,因此,dp[-1][k][0]=0
表示未有收益产生,dp[-1][k][1]=负无穷
保证 sell 不能被选到;状态为 1 (持有股票)时只能选择 buy,因此,dp[-1][k][1]=负无穷
保证 rest 不能被选到,dp[-1][k-1][0]=0
表示未有收益产生,此时选择 buy 表示买入起始股票,收益为负,即- prices[i]
。注意:索引 -1 为 0 的前一位,并非倒数最后一位,通常做添加一行(或一列)处理
-
若
dp
从i=1
开始,则需要考虑i=0
的情况,第 0 天有两种选择,rest(无操作)或 buy(买入),dp[0][k][0]=0
表示状态为 0 (未持有股票)选择 rest ;dp[0][k][1]=-prices[0]
表示状态为 1 (持有股票)选择 buy 表示买入起始股票,买入时收益为负。
-
-
返回值:
dp[n-1][K][0]
,表示状态为最后一天(n-1
)、最多交易次数(K
)、未持有股票(0
,已卖出股票)时的收益。
2. 分析完股票交易的背景,现在查看本题要求,K=1,则 k 有 0 或 1 两种状态。在 k = 0 时表示未有交易发生,则 dp[i-1][0][0]=0
。观察状态转移方程,其余状态只剩下 k=1,因此,简化状态转移方程如下:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])
附代码2(Python):
class Solution:
def maxProfit(self, prices):
if not prices:
return 0
n = len(prices)
# 初始化 i=0 的状态
dp = [[0]*2 for _ in range(n)]
dp[0][1] = -prices[0]
# 从 i=1 处开始动态转移
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]
test = Solution()
prices_li = [[7,1,5,3,6,4],
[7,6,4,3,1],
[1,2]
]
for prices in prices_li:
print(test.maxProfit(prices))
5
0
1
思路3:优化动态规划
- 观察上述状态转移方程,发现当天状态只与前一天状态有关,因此可采用单变量代替
dp
数组。
附代码3.1(Python):从 i=1 处开始迭代
class Solution:
def maxProfit(self, prices):
if not prices:
return 0
# 初始化状态
dp_i_0 = 0
dp_i_1 = -prices[0]
# 从 i=1 处开始迭代
for i in range(1, len(prices)):
dp_i_0 = max(dp_i_0, dp_i_1+prices[i])
dp_i_1 = max(dp_i_1, -prices[i])
return dp_i_0
test = Solution()
prices_li = [[7,1,5,3,6,4],
[7,6,4,3,1],
[1,2]
]
for prices in prices_li:
print(test.maxProfit(prices))
5
0
1
附代码3.2(Python):从 i=0 处开始迭代
class Solution:
def maxProfit(self, prices):
if not prices:
return 0
# 初始化状态
dp_i_0 = 0
dp_i_1 = float('-inf')
# 从 i=0 处开始迭代
for i in range(len(prices)):
dp_i_0 = max(dp_i_0, dp_i_1+prices[i])
dp_i_1 = max(dp_i_1, -prices[i])
return dp_i_0
test = Solution()
prices_li = [[7,1,5,3,6,4],
[7,6,4,3,1],
[1,2]
]
for prices in prices_li:
print(test.maxProfit(prices))
5
0
1
总结:
本题可以采用思路 1 的简单方法,介绍动态规划方法是因为 LeetCode 有一系列的股票题,可使用动态规划进行通解,可以更好地学习动态规划。
其他股票题的笔记如下:
LeetCode:121. 买卖股票的最佳时机(python)
LeetCode:122. 买卖股票的最佳时机 II(python)
LeetCode:123. 买卖股票的最佳时机 III(python)
LeetCode:188. 买卖股票的最佳时机 IV(python)
LeetCode:309. 最佳买卖股票时机含冷冻期(python)
LeetCode:714. 买卖股票的最佳时机含手续费(python)