鸽了好久了orz,有一阵子没跟上,后面越欠越多直接开摆了。 不能这样下去啦,接下来争取每天补1-2天,把剩下一半动态规划和图论刷完!尽量周末时候打周赛~
121. 买卖股票的最佳时机
思路1:贪心
贪心:取左边最小,右边最大的两天
class Solution:
def maxProfit(self, prices: List[int]) -> int:
low = float("inf") # 左侧最小
result = 0 # 最大利润
for i in range(len(prices)):
low = min(low, prices[i])
result = max(result, prices[i] - low)
return result
思路2:动态规划
动态规划,构建n*2的dp table,dp[i][0]表示第i天持有股票的最大现金,dp[i][1]是不持有股票的最大现金
对于dp[i][0],有两种状态转移过来,1 是前一天i-1持有到今天dp[i-1][0],另一种是前一天不持有,持有了今天的股票-prices[i]
对于dp[i][1],有两种状态转移过来,1 前一天不持有dp[i-1][1],另一种卖掉了i-1天持有的股票,dp[i-1][0]+prices[i]
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices: return 0
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], -prices[i]) # 持有
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]) # 不持有
return dp[n-1][1] # 最后一天不持有
思路3:动态规划-状态压缩
i只和i-1有关系,保留2*2的状态矩阵。遇到dp时候,i%2
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices: return 0
n = len(prices)
dp = [[0,0] for _ in range(2)]
dp[0][0] = -prices[0]
for i in range(1, n):
# 遇到dp需要把i%2
dp[i%2][0] = max(dp[(i-1)%2][0], -prices[i]) # 持有
dp[i%2][1] = max(dp[(i-1)%2][1], dp[(i-1)%2][0] + prices[i]) # 不持有
return dp[-1][1] # 最后一天不持有
122. 买卖股票的最佳时机 II
多次买入股票,一次只能有一笔交易。
思路:
本题与121题“买卖股票的最佳时机”的区别在于,本题中的股票可以买卖多次(但要注意只有一只股票,所以在再次购买之前必须先出售之前的股票)。在动态规划的五个步骤中,这个区别主要体现在递推公式上,其他步骤与121题相同。因此,我们将重点讲解递推公式。
这里重新说明一下dp数组的含义:
- dp[i][0]表示第i天持有股票所得的现金。
- dp[i][1]表示第i天不持有股票所得的最多现金。
如果第i天持有股票(dp[i][0]),那么可以由以下两个状态推导出来:
1. 第i-1天就持有股票,保持现状,所得现金就是昨天持有股票的所得现金,即:dp[i - 1][0]。
1. 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去今天的股票价格,即:dp[i - 1][1] - prices[i]。
注意,这里与121题“买卖股票的最佳时机”唯一不同的地方是,在推导dp[i][0]时,第i天买入股票的情况。在121题中,因为股票全程只能买卖一次,所以如果买入股票,第i天持有股票(dp[i][0])一定就是 -prices[i]。而本题中,因为一只股票可以买卖多次,所以当第i天买入股票时,所持有的现金可能有之前买卖过的利润。因此,第i天持有股票(dp[i][0]),如果是第i天买入股票,所得现金就是昨天不持有股票的所得现金减去今天的股票价格,即:dp[i - 1][1] - prices[i]。
再来看看如果第i天不持有股票(dp[i][1])的情况,同样可以由以下两个状态推导出来:
1. 第i-1天就不持有股票,保持现状,所得现金就是昨天不持有股票的所得现金,即:dp[i - 1][1]。
1. 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金,即:prices[i] + dp[i - 1][0]。
注意,这里与121题“买卖股票的最佳时机”的逻辑相同,卖出股票收获利润(可能是负值)是理所当然的。
以下是代码实现:(注意代码中的注释,标记了与121题“买卖股票的最佳时机”唯一不同的地方)
'''
思路:动态规划,dp n*2, dp[i][0]表示持有的最大现金,dp[i][1]表示不持有股票的最大现金,和买卖一次股票的区别在于,第i天购买股票时,需要考虑i-1天不持有的最大现金 dp[i-1][1] - prices[i]
'''
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices: return 0
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], dp[i-1][0] + prices[i]) # 不持有
return dp[-1][1]
思路2:滚动数组
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices: return 0
n = len(prices)
dp = [[0,0] for _ in range(2)]
dp[0][0] = -prices[0]
dp[0][1] = 0
for i in range(1, n):
dp[i%2][0] = max(dp[(i-1)%2][0], dp[(i-1)%2][1] - prices[i]) # 持有
dp[i%2][1] = max(dp[(i-1)%2][1], dp[(i-1)%2][0] + prices[i]) # 不持有
return dp[(n-1)%2][1] # 注意!,不是-1,如果prices是偶数就是-1,否则是0
注意,滚动数组最终返回的不是dp[-1][1]。
123. 买卖股票的最佳时机 III
前面两题一个是一组,一个是多组,现在又限制为两次交易了。
思路:
交易两次,状态要double!
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 买卖两次,需要5种状态,[不操作 第一次持有 第一次不持有 第二次持有 第二次不持有 ]
if not prices:
return 0
n = len(prices)
dp = [[0]* 5 for _ in range(n)]
dp[0][1] = -prices[0]
dp[0][3] = -prices[0]
for i in range(1, n):
dp[i][0] = dp[i-1][0] # 都是0吧
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) # 持有 扣钱
dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i]) # 不持有 赚钱
dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i]) # 持有 扣钱
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i]) # 不持有 赚钱
return dp[-1][4] # 注意:1 dp[-1][4]不是3,五种状态; 2 注意符号-+-+
滚动数组:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 买卖两次,需要5种状态,[不操作 第一次持有 第一次不持有 第二次持有 第二次不持有 ]
if not prices:
return 0
n = len(prices)
dp = [0]* 5
dp[1] = -prices[0]
dp[3] = -prices[0]
for i in range(1, n):
dp[1] = max(dp[1], dp[0] - prices[i]) # 持有 扣钱
dp[2] = max(dp[2], dp[1] + prices[i]) # 不持有 赚钱
dp[3] = max(dp[3], dp[2] - prices[i]) # 持有 扣钱
dp[4] = max(dp[4], dp[3] + prices[i]) # 不持有 赚钱
return dp[4]
加油!! 坚持1天!!