☀(day38)
前言:📜
这里包含了买卖股票的最佳时机Ⅰ~Ⅳ的题解,Ⅰ~Ⅳ难度逐渐上升。本文使用参考动态规划五部曲来对动态规划类进行系统解题,通过这四到例题体会动态规划的解题思路。
文章中题目分析主要说明题目的隐含意,一些定义的释义。解题思路含有是代码实现的理论分析
其中动态规划五部曲分为以下五步
- 分析确定dp数组以及其下标的含义或状态分析
- 确定递推公式
- 如何初始化dp数组
- 确定遍历的顺序
- 举例验证推导的dp数组(公式)是否正确
值得注意的是动态规划的题目比较灵活,这里的五步曲提供一个解题的框架,如有时候第一第二部可根据需要省略
目录
进入实战
🎯买卖股票最佳时机Ⅰ
📝题目:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
(1 <= prices.length <= 105)
本文中得交易都指一次买入一次出售
⭐示例 1:
输入:[7,1,5,3,6,4]
输出:5
说明:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6,因为你不能在买入前卖出股票。
⭐示例 2:
输入:prices = [7,6,4,3,1]
输出:0
说明:在这种情况下, 没有交易完成, 所以最大利润为 0。
🚩题目分析:
题目要求以最少的价钱买进股票然后以最高的价钱抛售股票来获取最高的利益且抛售股票要再买进股票前。(当然题目中的情况是不现实的,现实中很难预测古票的涨落)
💡解题思路:
一、🌟暴力解法
根据题目要求先买入股票后再卖出,可以将数组prices中的数两两相减,求其差值后用一个列表储存。两两相减的限制为数组prices中的后一个数 - 数前一个数。最后求储存差值列表的最大值。
🌈代码实现
def maxProfit(prices):
dp = []
for i in range(len(prices)):
for j in range(i, len(prices)):
dp.append(prices[j] - prices[i])
return max(dp) if max(dp) > 0 else 0
✏代码注释
def maxProfit(prices):
dp = [] # dp数组(列表)用于储存股票两两相减的差值
# 解题关键在于j的取值从i开始,也就限制了买进股票才能出售
for i in range(len(prices)):
for j in range(i, len(prices)):
dp.append(prices[j] - prices[i]) # 计算利润,prices[j]出售,prices[i]买入
return max(dp) if max(dp) > 0 else 0 # 利用max()函数求dp数组中最大值
return max(dp) if max(dp) > 0 else 0的写法相当于
if max(dp) > 0
return max(dp)
else:
return 0
如果dp长度为零说明没有交易产生,返回0
使用两层for循环,时间复杂度O(n^2)。使用一个列表储存差值,空间复杂度O(n)
二、🌟动态规划解法
动态规划五部曲
✨1.分析确定dp数组以及其下标的含义或状态分析
这里我们不使用dp数组,使用变量记录更新利润最大最小值
✨2.确定递推公式
我们找利润的最大值那么可以先卖入第一天的股票,以第二天股票的价格出售。记录这一次交易的利润,接着再买入第二天的股票,以第三天股票的价格出售,知道遍历整个价格列表prices。
那么这不是违背了题目的要求只能进行一次交易的要求了吗?
其实不然,比如我们在第二天出售股票后,又在第二天将他买入,接着在第三天出售股票。那么相当于在第二天我们我们什么都没做。我们这么进行交易的目的是找出交易获得的最大利润。
✨3.如何初始化dp数组(列表)
因为我们没有使用到数组,那么我们初始化用于记录交易的股票最低价和获得的最大利润
考虑到当我们不进行任何交易是买入的最低股价和利润都为0.
所以初始化两者都=0
✨4.确定遍历的顺序
从分析可知,遍历顺序从首到尾
✨5.举例验证推导的dp数组(公式)是否正确
可以带入例1验证
🌈代码实现
def maxProfit(prices):
min_input = prices[0]
max_profit = 0
for p in prices:
min_input = min(p, min_input)
max_profit = max(max_profit, p - min_input)
return max_profit if len(prices) > 1 else 0
✏代码注释
def maxProfit(prices):
min_input = prices[0] # 初始化最小买入价格
max_profit = 0 # 初始化最小利润
for p in prices: # 遍历股价列表prices
min_input = min(p, min_input) # 比较当前的股价和前面遍历的股价去最小值
max_profit = max(max_profit, p - min_input) # 不叫当前交易和之前交易的最大利润取最大值
return max_profit if len(prices) > 1 else 0
仅遍历一次prices列表,时间复杂度O(n)。使用变量记录,空间复杂度O(1).
🎯买卖股票最佳时机Ⅱ
📝题目:
给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。
在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股票。你也可以购买它,然后在 同一天 出售。返回 你能获得的 最大 利润 。(1 <= prices.length <= 3 * 104)
⭐示例 1:
输入: prices = [7,1,5,3,6,4]
输出: 7
说明: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。最大利益 = 4 + 3 = 7
⭐示例 2:
输入: prices = [1,2,3,4,5]
输出: 4
说明: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。最大利益 = 4
(注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,不满足最多只能持有一股股票的要求,你必须在再次购买前出售掉之前的股票。)
⭐示例 3:
输入: prices = [7,6,4,3,1]
输出: 0
说明: 在这种情况下, 没有交易完成, 所以最大利润为 0。(这里要求利润不能为负数)
🚩题目分析:
和买卖股票最佳时机Ⅰ不同之处在于Ⅱ可以进行多次买卖,但是Ⅱ要求在只能持有一股股票的情况下,进行购进和抛售,使得获取最大利益。可以在同一天买入和抛售,因为当天买卖股票的价格不变,所以这一天买卖股票的利益 = 0
💡解题思路:
一、🌟动态规划解法
上动态规划五部曲
✨1.分析确定dp数组以及其下标的含义
创建二维列表dp = [[0, 0], [0, 0]........[0, 0]]
为什么不创建一维列表?
因为我i们要表示两个状态:
dp[i][0]表示第i天不持有股票获得的最大利润
dp[i][1]表示第i天持有股票获得的最大利润
✨2.确定递推公式
根据上两个状态
1.第i天持有股票(dp[i][1])
那么可能的结果有:
(1)第i-1天也持有股票
第i-1天获得的最大利润为dp[i - 1][1]
(2)持有的股票是在第i-1天买入
第i-1天获得的最大利润为dp[i - 1][0] - prices[i]
递推得:第i天持有股票得到得最大利润为
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
prices[i]为股价
2.第i天不持有股票(dp[i][0])那么可能的结果有:
(1)第i-1天不持有股票
第i-1天获得的最大利润为dp[i - 1][0]
(2)不持有的股票因为在第i-1天抛售
第i-1天获得的最大利润为dp[i - 1][1] + prices[i]
递推得:第i天持有股票得到得最大利润为
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
那为什么第i天第状态不去考虑今天买入或者抛售?因为第i天买入和抛售的结果已经包含在持不持有股票中,为了得出递推公式我们考虑上一个状态。
最后不持有股票得状态即为本题的解(没抛售股票获得的利润一定小于抛售后)
✨3.如何初始化dp数组
由公式可知,dp[i][0], dp[i][1],是以dp[0][0], dp[0][1],为基础的
那么dp[0][0] = 0,(因为不持有股票)
dp[0][1] = - prices[0],(因为购入了股票)
✨4.确定遍历的顺序
显然,dp[i][x]的递推方式为顺推
✨5.举例验证推导的dp数组(公式)是否正确
可以以例1为例检验
🌈代码实现
def maxProfit(prices):
dp = [[0] * 2 for _ in range(len(prices))]
dp[0][0] = 0
dp[0][1] = - prices[0]
for i in range(1, len(prices)):
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][0]
✏代码注释
def maxProfit(prices):
# 创建二维数组用于储存持有或不持由股票时的利润
dp = [[0] * 2 for _ in range(len(prices))]
dp[0][0] = 0 # 初始化公式基础
dp[0][1] = - prices[0]
for i in range(1, len(prices)): # 由于dp[i-1]的限制,i从1开始
# 用max()取持有或不持由股票时利润最大值
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][0] # 返回最终不持由股票的最终状态
仅遍历一次prices列表,时间复杂度O(n)。使用数组记录,空间复杂度O(n).
二、🌟贪婪算法
在这多介绍一种方法-----贪婪(贪心)算法
以例1prices = [6,1,3,5]为例,最大利益 =(5-1)= 4,有很多人可能会想,我怎么知道要在第二天买入股票右在第四天卖出?实际上这会把问题复杂化。且看,换另一种计算方法 最大利益=(3-1)+(5-3)= 4.那么就有,我们只需要比较后一天的股票售价和前一天的股票售价,如果后一天的股票售价较高就可以出售。这就是贪婪算法。
根据贪婪算法(又叫贪心算法),我们只需比较后一天的股票售价和前一天的股票售价,如果后一天的股票售价较高就可以出售,出售获得的 利润=后一天股价 - 前一天股价
🌈代码实现
def maxProfit(prices):
ans = 0
for i in range(1, len(prices)):
if prices[i] > prices[i - 1]:
ans += prices[i] - prices[i - 1]
return ans
✏代码注释
def maxProfit(prices):
ans = 0 # 初始化利润
for i in range(1, len(prices)): # 由于prices[i-1]的限制,i从1开始。
if prices[i] > prices[i - 1]: # 比较前一天股价和后一天股价
ans += prices[i] - prices[i - 1] # 计算获得的利润
return ans # 返回结果
仅遍历一次prices列表,时间复杂度O(n)。使用变量记录,空间复杂度O(1).
🎯买卖股票最佳时机Ⅲ
📝题目:
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
⭐示例 1:
输入:prices = [1,2,3,4,5]
输出:4
说明:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
⭐示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
⭐示例 3:
输入:prices = [1,2,3,4,5]
输出:4
说明:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
🚩题目分析:
Ⅲ和Ⅱ不同在于Ⅲ要求找出在最多进行两次交易的情况下获得的利润最大值,这意味着我们可以进行一次交易,也可以进行两次交易。但是手中持有股票时必须先出售才能进行第二次购买。
💡解题思路:
动态规划解法
动态规划五部曲
✨1.分析确定dp数组以及其下标的含义或状态分析
这里不用dp数组,进行状态分析在第i天结束后,有下面5个状态
(1)不买也不卖
(2)进行了一次买进股票
(3)进行了一次买进和一次抛售(完成第一次交易)
(4)在完成第一次交易的前提下,进行第二次买进
(5)在完成第一次交易的前提下,完成第二次交易
注意:这些操作的最后一步并不一定是在第i天完成
✨2.确定递推公式
由上面的状态分析可知:
状态(1) 无利润产生为0(可以舍去)
状态(2)first_buy_profit
获得的利润 = max(day_before_profit, -prices[i])
其中day_befor_frofit表示前些天获得的总利润,-prices[i]表示第i天购进股票的开销
为什么状态(2)获取到的利润不是= -prices[i],状态(2)不是只有买进操作吗?
这里要注意,只有在第一天才能确定获取到的利润= -prices[1]。状态(2)虽然只有买进操作,但是这一次的买进操作可能在第i天前,比如第i-1天,第i-2天等等,不妨设在第i-2天买进,那么获的得利润 = max(day_before_profit, -prices[i-2])。
那么在第i-1天买入获的得利润 = max(day_before_profit, -prices[i-1])。
我们只取获得最大利润的那次买入,或者说是在计算出这次买入可以获得最大利润时才买入
当然只有买入操作利润必然<=0
由状态(2)以此递推:第i天结束后只进行一次买进操作(发生在第i-2天),这时候第i天获得的最大利润就等于 = max(day_before_profit, -prices[i])
对于状态(2)获得的利润为什么是= max(day_befor_profit, -prices[i]),-prices是负数,day_befor_profit不是一定>=-prices吗?
准确的说-prices不一定是负数可能是0,因为例子中有股价等于0的情况(虽然这个股价等于零的情况很离谱,但是做题也要依照题意),如果股价等于0,而day_before获得的利润为负数,此时-prices > day_before_profit,所以day_befor_profit不一定>=-prices。
状态(3)first_sell_profit
在状态(2)的基础上
获得的最大利润 = max(day_before_profit, first_buy_profit + prices[i])
其中day_befor表示前些天获得的总利润,+prices[i]表示第i天抛售的收入
状态(4)second_buy_profit
在状态(3)的基础上
获得的最大利润 = max(day_beforre_profit, first_sell_profit - prices[i])
其中day_befor_frofit表示前些天获得的总利润,-prices[i]表示第i天购进股票的开销
状态(5)second_sell_profit
在状态(4)的基础上
获得的最大利润 = max(day_before_profit, second_buy_profit + prices[i])
其中day_befor表示前些天获得的总利润,+prices[i]表示第i天抛售的收入
最后的递推公式为
first_buy_profit = max(first_buy_profit, -prices[i])
first_sell_profit = max(first_sell_profit, first_buy_profit + prices[i])
second_buy_profit = max(second_buy_profit, first_sell_profit - prices[i])
second_sell_profit = max(second_sell_profit , second_buy_profit + prices[i])
最后的递推公式将day_before_profit全部转化为了对应的first_buy_profit,first_sell_profit,
second_buy_profit,second_sell_profit.
first_buy_profit,first_sell_profit和day_before_profit的区别在于,它们包含了状态最后一步可能发生在第i天,而day_before_profit则不包含。
✨3.如何初始化dp数组
由递推公式可知,递推公式的基础为:
first_buy_profit,first_sell_profit,second_buy_profit,second_sell_profit
初始化
first_buy_profit = second_buy_profit = -prices [0]
first_sell_profit = second_sell_profit = 0
初始化first_buy_profit = -prices [0] 好理解,但是其他基础这样初始化的依据是什么呢?
对于初始化我们要考虑边界条件,对于第0天:
second_buy_profit为第2次购买,这时由在第0天进行第一次交易(买了又卖利润为0),然后又进行一次购买花费prices[i]。所以second_buy_profit = 0 +(-prices[0])= - prices[0]
first_sell_profit为第一次出售,之前先进行了一次购买,因为在都是在第0天进行,所以first_sell_profit = 0
second_sell_profit为第二次购买,在第0天进行第一次交易(买了又卖利润为0),然后又进行一次购买花费prices[0]又卖出,获得prices[0]所以second_sell_profit = 0
✨4.确定遍历的顺序根据递归公式,遍历顺序为从头到尾。
✨5.举例验证推导的dp数组(公式)是否正确
可以带入一个简单以的例子,比如例2.
那么最终的答案是什么呢?
可以肯定的是最终答案在first_sell_profit,second_sell_profit中,因为抛售后获得的利润一定会比将股票留在手里获得的利润高。
又因为上面分析到,可以在同一天买了又卖掉,所以当出现完成一次交易为最优解的情况时(如例4),first_sell_profit可以理解成在完成这一次交易后,又在当天(同一天)买了又卖掉,这不影响最终结果。所以second_sell_profit为最终结果。
🌈代码实现
def maxProfit(prices):
first_buy_profit = second_buy_profit = -prices[0]
first_sell_profit = second_sell_profit = 0
for i in range(1, len(prices)):
first_buy_profit = max(first_buy_profit, -prices[i])
first_sell_profit = max(first_sell_profit, first_buy_profit+ prices[i])
second_buy_profit = max(second_buy_profit, first_sell_profit - prices[i])
second_sell_profit = max(second_sell_profit, second_buy_profit + prices[i])
return second_sell_profit
✏代码注释
def maxProfit(prices):
# 初始化递归基础
first_buy_profit = second_buy_profit = -prices[0]
first_sell_profit = second_sell_profit = 0
# 运行递归公式,因为第0天已在初始化中讨论,所以i从1开始
for i in range(1, len(prices)):
first_buy_profit = max(first_buy_profit, -prices[i])
first_sell_profit = max(first_sell_profit, first_buy_profit + prices[i])
second_buy_profit = max(second_buy_profit, first_sell_profit - prices[i])
second_sell_profit = max(second_sell_profit, second_buy_profit + prices[i])
return second_sell_profit # 返回结果
仅遍历一次prices列表,时间复杂度O(n)。使用变量记录,空间复杂度O(1).
🎯买卖股票最佳时机Ⅲ
📝题目:
给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
⭐示例 1:
输入:k = 2, prices = [2,4,1]
输出:2
说明:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
⭐示例 2:
输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
说明:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
🚩题目分析:
Ⅳ综合了Ⅰ,Ⅱ,Ⅲ。是Ⅰ,Ⅱ,Ⅲ所有情况的总合。
给定整数k,和整数数组prices,在最多进行k次交易的前提下,交易获取最大利益。这里的一次交易指的是一次买进和一次出售。值得注意的是这里的k可能取到10^9甚至更多,然而对于过大的k是没有意义的,因为n天最多能进行[n/2]次交易,(这里的[]表示取整)。
为什么最多是[n/2]次交易?
因为n天进行超过[n/2]次交易会存在同一天买进和买出的情况。比如prices = [ 1,2,3],k = 2 > [3/2] = 1。取这时最大利润 = (2-1)+(3-1)= 2等同于,最大利润 = 3-1= 2。这时k = 1于k = 2获得的利润是一样的,第二天出现无效交易(在同一天买进又卖出,利润为0)。所以我们认为在不进行无效交易的情况下,n天最多能进行[n/2]次交易。
💡解题思路:
动态规划解法
进行动态规划五步曲
✨1.分析确定dp数组以及其下标的含义或状态分析
对于给定的整数k和数组prices,我们的第一直觉就是使用二维dp[i][j]数组。
现在我们规定:
buyProfit[i][j]表示在第i天进行了j次交易,且手上持有股票的状态下获取到的最大利润
sellProfit[i][j]表示在第i天进行了j次交易,且手上不持有股票的状态下获取到的最大利润
✨2.确定递推公式
接着进行状态的分析
对于buyProfit[i][j]
一、在第i天持有股票
(1)如果持有的股票是在第i天买入
那么在第i-1天包括第i-1天前,进行了j次交易(因为买入未构成一次交易)。在第i-1天不持有股票,在第i天买入股票需要花费prices[i]。
==>第i天最大利润
sellProfit[i - 1][j] - prices[i]
(2)如果持有的股票不是在第i天买入
那么在第i-1天包括第i-1天前,进行了j次交易(因为买入未构成一次交易)。在第i天无任何买卖。==> buyProfit[i - 1][j]
第i天最大利润
buyProfit[i][j] = max(buyProfit[i - 1][j],sellProfit[i - 1][j] - prices[i])
用max取两者最大值
二、在第i天不持有股票
(3)如果不持有的股票是因为在第i天卖出
那么在第i-1天时只进行了j-1次交易,在第i天卖出股票获得prices[i] 。
==>第i天最大利润
buyProfit[i - 1][j - 1] + prices[i]
(4)如果不持有的股票不是因为在第i天卖出
那么在第i-1天进行了j次交易,第i天无任何买卖。==>sell[i-1][j]
第i天最大利润
sellProfit[i][j] = max (sell[i-1][j],buyProfit[i - 1][j - 1] + prices[i])
递推公式:
buyProfit[i][j] = max(buyProfit[i - 1][j],sellProfit[i - 1][j] - prices[i])
sellProfit[i][j] = max (sell[i-1][j],buyProfit[i - 1][j - 1] + prices[i])
✨3.如何初始化dp数组
如何初始化dp(buyProfit,sellProfit)数组?
我们考虑边界条件和dp数组的基础,第0天和交易次数为0时
对于第0天(也就是第一天,为方便以下标表示第几天)
不论进行几次交易,利润都为0。
但是对于buyProfit手中持有股票的情况来说,在第0天一定是以prices[0]的价格买进股票。
所以初始化buyProfit[0][k]
当k = 0时
buyProfit[0][0] = -prices[0]
当k != 0时
我们知道无论k为多少,进行多少次交易都为无效交易,即利润为0.所以可以将buyProfit[0][k]初始化成不合理的数,如负无穷。这样在初始化用max取最大值时保证取到的是-prices[i](k != 0)
初始化sellProfit[0][k]
当k = 0时
sellProfit[0][0]一定等于0,这样才能满足在第0天不持又股票
当k != 0时
我们知道无论k为多少,进行多少次交易都为无效交易,所以也可以将buyProfit[0][k]初始化成不合理的数,如负无穷。
那么为什设置为负无穷而不设为0呢?
我们知道有些交易是会出现负利润的情况的,如果设为0,在max()取最大值时会取0,增大结果,使结果出错。
对于进行0次交易
初始化buyProfit[i][0]
因为是没有交易进行,那么我们只有两种操作,一种是不进行任何操作,第二种是只买进一次(这时没有卖的操作还不算交易)。
不进行任何操作
继承前一天的状态==>buyProfit[i-1][0]
只买进一次
sellProfi[i-1][0] - prices[i]
初始化sellProfit[i][0]
不进行任何操作
sellProfit[i-1][0]
只买进一次
因为sellProfit表示不持有股票的状态但是与买进一次矛盾所以可以去掉这一状态
最终初始化
buyProfit[0][0] = -prices[0]
sellProfit[0][0] = 0
buyProfit[0][i] = float("-inf")
sellProfit[0][i] = float("-inf")
buyProfit[i][0] = max(buyProfit[i - 1][0], sellProfit[i - 1][0] - prices[i])
sellProfit[i][0] = sellProfit[i - 1][0]
✨4.确定遍历的顺序由递推公式可知,递推方向为首到尾
✨5.举例验证推导的dp数组(公式)是否正确
可以带入一个简单以的例子,比如例1.
那么最终的答案是什么呢?
可以肯定的是最终答案在sellProfit[n-1][1.....k](n为数组prices的长度)中,即递推完成状态。因为抛售后获得的利润一定会比将股票留在手里获得的利润高。所以最后返回sellProfit[n-1][1.....k]最大值即可。
return max(sellProfit[n-1])
🌈代码实现
def maxProfit(k, prices):
if not prices:
return 0
n = len(prices)
k = min(k, n // 2)
buyProfit = [[0] * (k + 1) for _ in range(n)]
sellProfit = [[0] * (k + 1) for _ in range(n)]
buyProfit[0][0] = -prices[0]
sellProfit[0][0] = 0
for i in range(1, k + 1):
buyProfit[0][i] = float("-inf")
sellProfit[0][i] = float("-inf")
for i in range(1, n):
buyProfit[i][0] = max(buyProfit[i - 1][0], sellProfit[i - 1][0] - prices[i])
sellProfit[i][0] = sellProfit[i - 1][0]
for j in range(1, k + 1):
buyProfit[i][j] = max(buyProfit[i - 1][j], sellProfit[i - 1][j] - prices[i])
sellProfit[i][j] = max(sellProfit[i - 1][j], buyProfit[i - 1][j - 1] + prices[i])
return max(sellProfit[n - 1])
✏代码注释
def maxProfit(k, prices):
if not prices: # 空数组,则返回0
return 0
n = len(prices)
k = min(k, n // 2) # 取最大交易次数
buyProfit = [[0] * (k + 1) for _ in range(n)] # 创建dp二维数组
sellProfit = [[0] * (k + 1) for _ in range(n)]
buyProfit[0][0] = -prices[0] # 初始化第0天和无交易时dp数组
sellProfit[0][0] = 0
# 初始化第0天且k>0时的dp数组
for i in range(1, k + 1): # i=0已在初始化中讨论
buyProfit[0][i] = float("-inf") # floa("-inf")表示负无穷,floa("+inf")标识正无穷,floa("inf")表示无穷
sellProfit[0][i] = float("-inf")
# 初始化无交易状态(仅买进不算交易)
for i in range(1, n): # i=0已在初始化中讨论,且受限于[i-1],所以i从1开始
buyProfit[i][0] = max(buyProfit[i - 1][0], sellProfit[i - 1][0] - prices[i])
sellProfit[i][0] = sellProfit[i - 1][0] # 等价于sellProfit[i][0] = 0
# 带入dp递归公式
for j in range(1, k + 1): # 且受限于[j-1],所以j从1开始
buyProfit[i][j] = max(buyProfit[i - 1][j], sellProfit[i - 1][j] - prices[i])
sellProfit[i][j] = max(sellProfit[i - 1][j], buyProfit[i - 1][j - 1] + prices[i])
return max(sellProfit[n - 1])
这里说明一下为什么创建dp数组时是[0]*(k+1)而不是[0]*k。对于单调递减的数组,如果交易就会亏钱,所以不交易才能获得最大利益。这时候最多进行k次交易在加上不交易这种情况dp数组的维数就是k+1.
💫代码优化
注意到buyProfit[i][j] 和 sell[i][j]sell[i][j] 都从 buy[i-1][..]以及sell[i−1][..] 转移而来,因此我们可以使用一维数组而不是二维数组进行状态转移,这样能提高解题效率。
这里说的转移就是状态的变换,从状态[i-1]到状态[i]的变化。
buyProfit[i][j] = max(buyProfit[i - 1][j], sellProfit[i - 1][j] - prices[i])
可转化为
buyProfit[j] = max(buyProfit[j], sellProfit([j] - prices[i])
----------------------------------------------------------------------------------------------
sellProfit[i][j] = max(sellProfit[i - 1][j], buyProfit[i - 1][j - 1] + prices[i])
可转化为
sellProfit[j] = max(sellProfit[j], buyProfit[j - 1] + prices[i])
考虑状态覆盖的问题(赋值先后)
对于转换后的dp公式
buyProfit[j] = max(buyProfit[j], sellProfit([j] - prices[i])
sellProfit[j] = max(sellProfit[j], buyProfit[j - 1] + prices[i])
因为先计算buyProfit[j],所以当下一步计算sellProfit[j]时sellProfit[j]公式中的buyProfit[j - 1]的值已经被覆盖了。从二维数组理解就是
sellProfit[j]公式中的buyProfit[j - 1]本来应当是从二维表示中的buyProfit[i-1][j-1]转移而来,而现在却变成了 buyProfit[i][j−1]。
但是实际上它仍是正确的
考察buyProfit[i][j−1] = max(buyProfit[i - 1][j-1], sellProfit[i - 1][j-1] - prices[i])
仍是从状态[i-1]到状态[i]的变化,所以等效于buyProfit[j - 1]
🌈代码实现
def maxProfit(k, prices):
if not prices:
return 0
n = len(prices)
k = min(k, n // 2)
buyProfit = [0] * (k + 1)
sellProfit = [0] * (k + 1)
buyProfit[0], sellProfit[0] = -prices[0], 0
for i in range(1, k + 1):
buyProfit[i] = sellProfit[i] = float("-inf")
for i in range(1, n):
buyProfit[0] = max(buyProfit[0], sellProfit[0] - prices[i])
for j in range(1, k + 1):
buyProfit[j] = max(buyProfit[j], sellProfit[j] - prices[i])
sellProfit[j] = max(sellProfit[j], buyProfit[j - 1] + prices[i])
return max(sellProfit)
✏代码注释
def maxProfit(k, prices):
if not prices: # 空数组,则返回0
return 0
n = len(prices)
k = min(k, n // 2) # 取最大交易次数
buyProfit = [0] * (k + 1) # 创建dp一维数组
sellProfit = [0] * (k + 1)
buyProfit[0], sellProfit[0] = -prices[0], 0 # 初始化第0天时dp数组
for i in range(1, k + 1):
buyProfit[i] = sellProfit[i] = float("-inf")
for i in range(1, n): # i=0已在初始化中讨论,且受限于[i-1],所以i从1开始
buyProfit[0] = max(buyProfit[0], sellProfit[0] - prices[i])
for j in range(1, k + 1): # 且受限于[j-1],所以j从1开始
buyProfit[j] = max(buyProfit[j], sellProfit[j] - prices[i])
sellProfit[j] = max(sellProfit[j], buyProfit[j - 1] + prices[i])
return max(sellProfit)
希望大家可以学会递归,今天就到这,明天见。🚀
❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄end❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄