股票买卖最大利润问题:动态规划解法
本文将介绍如何通过动态规划解决经典的“股票买卖最大利润”问题,并通过代码实例进行详细分析。
问题描述
给定一个整数数组 prices
,其中 prices[i]
表示某天的股票价格,你可以选择在某些天买入和卖出股票,且每次只能买入一次,卖出一次,求你能够获得的最大利润。
解释
- 你有
n
天的股票价格,你可以选择在任意一个时间点买入和卖出股票。 - 买入和卖出操作必须满足:买入操作发生在卖出操作之前。
- 你可以进行多次买入卖出操作,但不能同时持有多只股票。
动态规划解法
状态定义
我们可以使用两个变量来表示不同的状态:
f0
:表示不持有股票的最大利润。f1
:表示持有股票的最大利润。
状态转移方程
在每一天,最优策略是基于前一天的状态决定的。因此,我们有以下两种状态转移:
-
更新
f0
:如果今天你没有持有股票,可能是:- 保持不持有状态,
f0
不变; - 卖出股票,获得利润,更新
f0
为f1 + p
,其中p
是今天的股价。
所以,更新
f0
的公式为:
[
f0 = \max(f0, f1 + p)
] - 保持不持有状态,
-
更新
f1
:如果今天你持有股票,可能是:- 保持持有状态,
f1
不变; - 从没有持有股票转变为持有股票,更新
f1
为f0 - p
,即买入股票。
所以,更新
f1
的公式为:
[
f1 = \max(f1, f0 - p)
] - 保持持有状态,
初始化
- 初始时,没有持有股票,因此
f0 = 0
。 - 初始时,持有股票是不可能的,因此
f1 = -inf
(负无穷,表示不可达状态)。
最终结果
最终返回 f0
,即在所有交易结束后,且没有持有股票时的最大利润。
代码实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
f0 = 0 # 不持有股票的最大利润
f1 = float('-inf') # 持有股票的最大利润
# 遍历每一天的股票价格
for p in prices:
# 如果今天没有持有股票,最大利润是保持不持有或卖出股票
new_f0 = max(f0, f1 + p)
# 如果今天持有股票,最大利润是保持持有或买入股票
f1 = max(f1, f0 - p)
# 更新不持有股票的最大利润
f0 = new_f0
# 返回最终不持有股票时的最大利润
return f0
代码解析
-
初始化:
f0 = 0
,表示开始时没有持有股票,利润为零。f1 = -inf
,表示开始时不可能有持有股票的利润。
-
遍历价格数组:
- 每一天更新
f0
和f1
的值:new_f0 = max(f0, f1 + p)
:如果不持有股票,今天的利润是“保持不持有”或“卖出股票”中的较大值。f1 = max(f1, f0 - p)
:如果持有股票,今天的利润是“保持持有”或“买入股票”中的较大值。
- 更新
f0
,即不持有股票的最大利润。
- 每一天更新
-
返回结果:
- 最终返回
f0
,即不持有股票时的最大利润。
- 最终返回
图示解析
如上图所示,动态规划通过不断更新 f0
和 f1
,考虑每一天的买入卖出决策,从而推导出最终的最大利润。
时间和空间复杂度
- 时间复杂度:O(n),其中
n
是prices
数组的长度。我们只遍历一次prices
数组。 - 空间复杂度:O(1),只使用了常数空间来存储
f0
和f1
。
总结
通过动态规划的思想,我们能够高效地解决这个股票买卖问题。通过维护两个状态变量 f0
和 f1
,我们可以从前一天的状态推导出最优的买卖决策,最终得到最大利润。相比暴力搜索的方法,这种方法更加高效,时间复杂度为 O(n)。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n=len(prices)
f0=0
f1=-inf#初始化边界 因为下一次的最优赚钱方案一定之和上一次的状态有关系,那么可以用两个int变量来存储
for p in prices:
new_f0=max(f0,f1+p)#如果下一次是没有持有的,那么下一次的最大值肯定是”保持没有持有“或者“持有但是卖出”里面选一
f1=max(f1,f0-p)#这里设置new_f0的原因是,我的f1依赖于上一次的状态,如果new_f0就变成依赖想和一次的状态,显然不对
f0=new_f0#如果下一次是持有的,那么下一次的最大值肯定是“保持持有”或者“没有持有但是买入,但是买入可以让下次赚更多,因为是互相关联的”
return f0