买卖股票最佳时机1:
解法1:(暴力法)
首先,最简单的方法,暴力法:
两个循环,枚举所有买股票和买股票的可能,及其他们的收益。取最大的收益。
class Solution:
def maxProfit(self, prices) -> int:
profit = 0
for i in range(len(prices)):
for j in range(i+1,len(prices)):
profit = max(profit,prices[j]-prices[i])
print(profit)
l=[6,2,8,1,5]
ss = Solution()
ss.maxProfit(l)
时间复杂度:O(N^2),N为价格数组的长度。
空间复杂度:O(1)
解法二:动态规划
若含有从列表的左到右扫过来的特征的,可以用动态规划。
如下面代码的 min_prices为例,意思是在遍历列表时,一边遍历一边找最小值,那么当前最小值等min(之前的最小值,当前值),同样 max_profit也是同样原理。因此可以用动态规划实现。因为动态规划的意思就是 当前的状态可以由之前的状态和现在状态决定。
class Solution:
def maxProfit(self, prices) -> int:
if prices == []: return 0
n = len(prices)
max_profit = 0
min_prices = prices[0]
for i in range(n):
min_prices = min(min_prices,prices[i])
max_profit = max(max_profit,prices[i]-min_prices)
return max_profit
l=[6,2,8,1,5]
ss = Solution()
ss.maxProfit(l)
时间复杂度:O(N)
空间复杂度:O(1)
==============================================================================
买卖股票最佳时机2:
思路:由于可以做多次交易,所以相当于求多个阶段的收益,如下图,例如 8大于1,所以就开始结算之前的收益。如果8的后面是9的话,比8大,那就继续往前,直接下一个状态比其小,才开始结算收益。
class Solution:
def maxProfit(self, prices) -> int:
if prices == []: return 0
max_price = prices[0]
min_prices = prices[0]
profit = 0
n = len(prices)
for i in range(n):
if max_price > prices[i]:
profit += (max_price-min_prices)
max_price = prices[i]
min_prices = prices[i]
else:
max_price = prices[i]
min_prices = min(min_prices, prices[i])
if i == n-1:
profit += (max_price - min_prices)
return profit
l=[6,2,8,1,5]
ss = Solution()
res = ss.maxProfit(l)
print(res) #10
时间复杂度:O(N) 空间复杂度:O(1)
==============================================================================
买卖股票最佳时机3:
[动态规划思路]:
动态规划的思路就是 “当前的状态由之前的状态决定”。所以只需要设置好初始条件,就可以推出任何时刻的状态。
而且动态规划有一种“暴力法”的味道在里面,因为未经空间优化的动态规划,是维护一个数组dp,此数组包含了所有情况。
回归正题:
我们已知的条件是:
1. 一共是 n 天。
2. 最多只能交易2次。
那么我们可以给状态数组dp开始定义(这步我觉得是比写代码难的),我们把 dp 设成三维的,dp[X][Y][Z]:
第一维X:表示天数,既然是 n 天,那么X的最大长度就是 n-1 (因为数组是从0开始计数的)
第二维Y:表示是否持股,长度为2,要么是0,表示没持股,要么是1,表示持股。
第三维Z:表示已经交易了的次数,长度为3,分别表示没交易过,交易了一次,交易了两次。
所以数组dp 记录了每种情况下的收益。
我们的步骤是:
1.先初始化初始条件,即第0天时,各个情况的收益:
#第一天休息
dp[0][0][0]=0
#第一天买入
dp[0][1][0]=-prices[0]
# 第一天不可能已经有卖出
dp[0][0][1] = float('-inf') # -inf在python中表示负无穷大
dp[0][0][2] = float('-inf')
#第一天不可能已经卖出
dp[0][1][1]=float('-inf')
dp[0][1][2]=float('-inf')
2. 根据初始条件(第一天的情况),逐步开始推导第二天、第三天.....的情况:
for i in range(1,length):
#未持股,未卖出过,说明从未进行过买卖
dp[i][0][0]=0
#未持股,卖出过1次,可能是今天卖的,可能是之前卖的
dp[i][0][1]=max(dp[i-1][1][0]+prices[i],dp[i-1][0][1])
#未持股,卖出过2次,可能是今天卖的,可能是之前卖的
dp[i][0][2]=max(dp[i-1][1][1]+prices[i],dp[i-1][0][2])
#持股,未卖出过,可能是今天买的,可能是之前买的
dp[i][1][0]=max(dp[i-1][0][0]-prices[i],dp[i-1][1][0])
#持股,卖出过1次,可能是今天买的,可能是之前买的
dp[i][1][1]=max(dp[i-1][0][1]-prices[i],dp[i-1][1][1])
#持股,卖出过2次,不可能
dp[i][1][2]=float('-inf')
3. 最后从最后一天的 交易1次的收益 和 交易2次的收益中取最大的那个即可,当然收益必须大于0,否则最高收益是0
完整代码:
class Solution:
def maxProfit(self, prices):
if prices==[]:
return 0
length=len(prices)
#结束时的最高利润=[天数][是否持有股票][卖出次数]
dp=[ [[0,0,0],[0,0,0] ] for i in range(0,length) ]
#第一天休息
dp[0][0][0]=0
#第一天买入
dp[0][1][0]=-prices[0]
# 第一天不可能已经有卖出
dp[0][0][1] = float('-inf')
dp[0][0][2] = float('-inf')
#第一天不可能已经卖出
dp[0][1][1]=float('-inf')
dp[0][1][2]=float('-inf')
for i in range(1,length):
#未持股,未卖出过,说明从未进行过买卖
dp[i][0][0]=0
#未持股,卖出过1次,可能是今天卖的,可能是之前卖的
dp[i][0][1]=max(dp[i-1][1][0]+prices[i],dp[i-1][0][1])
#未持股,卖出过2次,可能是今天卖的,可能是之前卖的
dp[i][0][2]=max(dp[i-1][1][1]+prices[i],dp[i-1][0][2])
#持股,未卖出过,可能是今天买的,可能是之前买的
dp[i][1][0]=max(dp[i-1][0][0]-prices[i],dp[i-1][1][0])
#持股,卖出过1次,可能是今天买的,可能是之前买的
dp[i][1][1]=max(dp[i-1][0][1]-prices[i],dp[i-1][1][1])
#持股,卖出过2次,不可能
dp[i][1][2]=float('-inf')
return max(dp[length-1][0][1],dp[length-1][0][2],0)
s = Solution()
res = s.maxProfit([8,7,4,7,5,4,8,16,2])
print(res) #15
为了理解,下面给出数组dp的内容,还是以上述代码的例子为例:
里面记录着各种情况的收益,。-inf是无穷的意思,表示不可能事件。
[
[[0, -inf, -inf], [-8, -inf, -inf]], #第一天
[[0, -1, -inf], [-7, -inf, -inf]], #第二天
[[0, -1, -inf], [-4, -5, -inf]], #第三天
[[0, 3, 2], [-4, -5, -inf]],
[[0, 3, 2], [-4, -2, -inf]],
[[0, 3, 2], [-4, -1, -inf]],
[[0, 4, 7], [-4, -1, -inf]],
[[0, 12, 15], [-4, -1, -inf]],
[[0, 12, 15], [-2, 10, -inf]] #最后一天
]
【简化版】:
上面的版本是动态规划的基础版本,因为数组dp没优化。但是我们仔细看可以看出,数组dp基本上只用了一次,所以可以用几个遍历表示:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices) <= 1: return 0
#n_01表示第n天,0表示没持股,1表示已经交易了1次
n_01, n_02, n_10, n_11 = float("-inf"), float("-inf"), -prices[0], float("-inf")
for i in range(1, len(prices)):
c_01 = max(n_01, n_10+prices[i])
c_02 = max(n_02, n_11+prices[i])
c_10 = max(n_10, 0 - prices[i])
c_11 = max(n_11, n_01-prices[i])
n_01, n_02, n_10, n_11 = c_01, c_02, c_10, c_11
return max(n_01, n_02, 0)