leetcode123 问题链接 https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/
给定一些天数的股票价格list, 最多k次买入卖出, 求能获得的最大利润。 (买入前必须先卖出已有的股票)
解决思路:动态规划
令dp[i][j]表示最多i次交易(买入卖出算一次交易),前j天所能获得的最大利润。
dp[i][j] = max(dp[i][j-1], prices[j] - prices[x] + dp[i-1][x]) (x < j)
其中,逗号左边的dp[i][j-1] 表示第j天不卖出股票,逗号右边表示第j天卖出股票,dp[i][j]将取两者中的较大者。
逗号左边的容易理解,来看一下逗号右边的式子 prices[j] - prices[x] + dp[i-1][x]
其中, prices[j]是第j天卖出股票得到的收入,而在卖出这支股票前,肯定进行过买入的操作,-prices[x]是减去第x天买入所花的钱,由于买入在卖出之前,所以x < j,prices[j] - prices[x] 表示当前这次交易所得的利润。同时还要加上在这次交易之前所获得的最大利润,即最多进行i-1次交易,截止到第x天所能获得的最大利润,这是当前问题的子问题dp[i-1][x]。
由于x<j, x是一个变量,需要采用一些小技巧来降低计算复杂度,否则在遍历i, j的过程中还要对x进行遍历。
令第j-1轮求得的max( dp[i-1][x] - prices[x] ) (x < j) 存储在tmpMax变量中,则在第j轮迭代过程中
dp[i][j] = max( dp[i][j-1], prices[j] - prices[x] + dp[i-1][x] ) (x < j)
= max( dp[i][j-1], prices[j] + max( dp[i-1][x]- prices[x] ) (x < j) )
= max(dp[i][j-1], prices[j] + tmpMax)
在第j轮结束后,更新tmpMax的值以备第j+1轮使用: tmpMax = max(tmpMax, dp[i-1][j]- prices[j] )
python代码为:
class Solution:
def maxProfit(self, k, prices):
if not prices: return 0
dp = [ [0]*len(prices) for i in range(k+1) ]
# 初始化,k=0时候没有交易,利润为0; j=0时只有一天的数据,无论如何利润为0
for i in range(1, k+1):
tmp = dp[i-1][0] - prices[0] #initial
for j in range(1, len(prices)):
dp[i][j] = max(dp[i][j-1], prices[j] + tmp)
tmp = max(tmp, dp[i-1][j] - prices[j])
return dp[-1][-1]
时间复杂度O(kn). 空间复杂度O(kn), k是最多交易次数,n是股票价格列表的长度(天数)。当k很大时,内存会溢出。时间也会超限。下面对空间和时间进行优化。
首先对空间进行优化,由于dp数组每一行更新只用到了上一行的数据,因此dp数组可以由O(kn)降为O(n)
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
if not prices: return 0
dp = [0] * len(prices)
for i in range(1, k+1):
tmp = dp[0] - prices[0]
for j in range(1, len(prices)):
tmp2 = max(tmp, dp[j] - prices[j])
dp[j] = max(dp[j-1], prices[j] + tmp)
tmp = tmp2
return dp[-1]
改成这样之后,发现内存满足了要求,但是时间超限,继续优化时间。思路是当最大交易次数超过数组长度的一半时,意味着对于当前数组可以进行无限次的交易(卖出之前先要买入,买入之前手里不能有其他股票)。这时候问题转化成leetcode122,有成熟的O(n)时间复杂度的解法。因此加入一个判断,把问题分成k > len(prices)//2和k<= len(prices)//2两种情况来节约一些时间。
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
if not prices: return 0
def helper(prices): #可以无限次买入卖出
profit = 0
for i in range(1, len(prices)):
if prices[i] > prices[i-1]:
profit += prices[i]-prices[i-1]
return profit
if k > len(prices)//2:
return helper(prices)
dp = [0] * len(prices)
for i in range(1, k+1):
tmp = dp[0] - prices[0]
for j in range(1, len(prices)):
tmp2 = max(tmp, dp[j] - prices[j])
dp[j] = max(dp[j-1], prices[j] + tmp)
tmp = tmp2
return dp[-1]
这时时间复杂度和空间复杂度都满足了要求。