Python每日一练-----买卖股票的最佳时机Ⅳ
⛅(day20)
目录
🖍题目:
题目分析:
解题思路:
🌈动态规划解法
✏代码注释
🌈代码优化
代码实现
✏代码注释
🖍题目:
给定一个整数数组 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]
(1)如果持有的股票是在第i天买入
那么在第i-1天包括第i-1天前,进行了j次交易(因为买入未构成一次交易)。在第i-1天不次有股票,在底i天买入股票需要花费prices[i]。==> sellProfit[i - 1][j] - prices[i]
(2)如果持有的股票不是在第i天买入
那么在第i-1天包括第i-1天前,进行了j次交易(因为买入未构成一次交易)。在第i天无任何买卖。==> buyProfit[i - 1][j]
buyProfit[i][j] = max(buyProfit[i - 1][j],sellProfit[i - 1][j] - prices[i])用max取两者最大值
对于sellProfit[i][j]
(1)如果不持有的股票是因为在第i天卖出
那么在第i-1天时只进行了j-1次交易,在第i天卖出股票获得prices[i] 。==>buyProfit[i - 1][j - 1] + prices[i]
(1)如果不持有的股票不是因为在第i天卖出
那么在第i-1天进行了j次交易,第i天无任何买卖。==>sell[i-1][j]
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] # 带入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❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄