LeetCode - Best Time To Buy and Sell Stock Series (I, II, III, IV, with Cooldown)

来源:互联网 发布:诉苦大会 知乎 编辑:程序博客网 时间:2024/05/16 17:22

LeetCode - Best Time to Buy and Sell Stock 系列 解题报告

本文记录笔者在解LeetCode上的Best Time to Buy and Sell Stock系列时的一些想法。该系列题目共有五道,它们背景相似,但因为条件限制不同,解决思路也不太相同。

I

  1. Best Time to Buy and Sell Stock

    https://leetcode.com/problems/best-time-to-buy-and-sell-stock/

题目大意是,给定一个关于每天股票价格的序列,只能买卖一次,求最大利润。

我首先想到的是求相邻两天的价格的差值,然后求这个关于差值的数列的最大子数列(53. Maximum Subarray)。

具体思路是DP,让dp[i]为以第i个元素为结尾的最大的子数列,那么有,

dp[i]={dp[i1]+arr[i] if dp[i1] is positivearr[i] otherwise

这可以在O(n)内完成。

class Solution(object):    def maxProfit(self, prices):        arr=[]        curr_max=0        last_max=0        for i in range(len(prices)-1):            curr=prices[i+1]-prices[i]            if last_max > 0:                last_max += curr            else:                last_max = curr            curr_max = max(curr_max,last_max)        return curr_max 

后来看了其他人的答案,其实这可以用全局最优和局部最优的角度去考虑。如下面重写的代码中glo(bal)全局最优只有在小于loc(al)局部最优时才会被更新。当局部最优某天小于0,说明新的最低点发现了,这时候可以将局部最优置为0,说明局部最优的买入点要更新成这个新的最低点了。

这同样是O(n)的时间。

class Solution(object):    def maxProfit(self, prices):        glo=0        loc=0        for i in range(1,len(prices)):            delta = prices[i]-prices[i-1]            loc += delta            if loc < 0:                loc = 0            if loc > glo:                glo = loc        return glo

II

  1. Best Time to Buy and Sell Stock II

    https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/

这也是给定了一个价格的序列,只不过允许任意次数的交易

这个就很简单了,既然不限次数,那么每个相邻交易日,能赚的都赚了。

class Solution(object):    def maxProfit(self, prices):        """        :type prices: List[int]        :rtype: int        """        result=0        for i in range(len(prices)-1):            d=prices[i+1]-prices[i]            if d > 0:                result+=d        return result

III

  1. Best Time to Buy and Sell Stock III

    https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/

这道题的限制是,最多只能买卖两次

我的做法是求所有[0, …, i]的子数列只买卖一次的大小,做法和Best Time to Buy and Sell Stock I 是一样的,结果放在pre[i]中。这个只需要扫描一遍整个数组即可。

再求所有[i, …, n-1]的子数列只买卖一次的大小,这个做法和上面的是相似的,只不过我们要从n-1开始,倒着扫描数组,并每次记录最糟糕的买卖情况(因为倒序买卖的最差情况,就对应着正序买卖的最好情况)。结果放在post[i]中。

上面两个过程都是O(n)时间的,那么我再求maxi(pre[i]+post[i+1])。这个过程也是线性的。因此整个算法,总体时间复杂度是O(n)的。

class Solution(object):    def maxProfit(self, prices):        if len(prices) == 0 or len(prices) == 1:            return 0        pre = [0 for i in range(0, len(prices))]        post = [0 for i in range(0, len(prices))]        glo = 0        loc = 0        for i in range(1, len(prices)):            loc += prices[i] - prices[i - 1]            if loc < 0:                loc = 0            glo = max(glo, loc)            pre[i] = glo        glo = 0        loc = 0        for i in range(len(prices)-1,0,-1):            loc += prices[i-1]-prices[i]            if loc > 0:                loc = 0            glo = min(glo,loc)            post[i-1] = - glo        print(pre)        print(post)        curr = max(pre[len(prices)-1],post[0])        for i in range(1,len(prices)-1):            curr = max(curr,pre[i]+post[i+1])        return curr

IV

  1. Best Time to Buy and Sell Stock IV

    https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/

这次的限制是,最多可以做k次买卖

这个我就不会了,后来参考了一下网上的解法。

Best Time to Buy and Sell Stock III – LeetCode

http://blog.csdn.net/linhuanmars/article/details/23236995

这里介绍的解法就是DP,但状态的定义比较巧妙。总体思路也是一种局部最优、全局最优的方法。

global[i][j]表示前i天最多做j次买卖的全局最优值,local[i][j]表示前i天最多做j次买卖且第i天是需要卖出的。

它们的关系,有

global[i][j]=max(global[i1][j],local[i][j])

这条式子是以第i天是否有卖出动作作为标准的。

local[i][j]=max(global[i1][j1]+max(diff,0),local[i1][j]+diff)

其中diff是第i天和i-1天的差价。

这个算法时间复杂度是O(nk),如果令k=2可以直接获得第III的解法。

class Solution(object):    def maxProfit(self, k, prices):        if len(prices) == 0 or len(prices) == 1:            return 0        if k == 0:            return 0        if k >= len(prices):            acc = 0            for i in range(1,len(prices)):                if prices[i] - prices[i-1] > 0:                    acc += prices[i]-prices[i-1]            return acc        glo = [[0, 0] for i in range(len(prices))]        loc = [[0, 0] for i in range(len(prices))]        # j == 1        best = 0        loc_maxima = 0        for i in range(1, len(prices)):            delta = prices[i] - prices[i - 1]            loc_maxima += delta            if loc_maxima < 0:                loc_maxima = 0            best = max(best, loc_maxima)            loc[i][1] = loc_maxima            glo[i][1] = best        # j > 1        for j in range(2, k + 1):            for i in range(1, len(prices)):                diff = prices[i] - prices[i - 1]                loc[i][j%2] = max(glo[i - 1][(j - 1)%2] + max(diff, 0), loc[i - 1][j%2] + diff)                glo[i][j%2] = max(loc[i][j%2], glo[i - 1][j%2])        print(glo)        print(loc)        return glo[len(prices) - 1][k%2]

with Cooldown

  1. Best Time to Buy and Sell Stock with Cooldown

    https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldow/

这道题的要求是,允许做任意多次交易,但在做卖出操作后的一天,是不能在做买入了,是为cooldown。

我一开始写了个贪心,但这是错的(我太天真了)。

后来我参考了这里,

Share my thinking process

https://discuss.leetcode.com/topic/30421/share-my-thinking-process

恰好该文作者也是该题目的作者。

解答用的是DP,定义了三种状态buy[],sell[]和rest[]。

先说明一下,所谓的交易序列就是类似于[buy, sell, rest, buy, rest …]的序列,其中元素分别代表该天做的动作。我们不用担心会出现在同一天出现buy和sell,因为这不能带来利润,且还带来额外的cooldown限制。

buy[i]的意思是,从第0天到第i天里面所有的以buy为结尾的交易序列中的最优解(最大利润),比如[buy,sell,buy],[rest,rest,buy]。注意[buy, rest, rest]也可以算这种序列。这里会让人产生疑惑,这和rest[i]不就冲突了吗,我们待会再看。

sell[i]和rest[i]意思是类似的。

那么这里就有状态转移,

buy[i]=max(rest[i1]price,buy[i1])
sell[i]=max(buy[i1]+price,sell[i1])
rest[i]=max(sell[i1],buy[i1],rest[i1])

buy[i]代表的最优解,可以从第i天是否买入来分类。换言之,比较从rest[i-1]代表的最优解减去i天价格,和第i天不做买入操作(rest)的结果。buy[i-1]代表的交易序列其实也是满足buy[i]的交易序列的要求的,因为序列最后一个操作还是buy(忽略尾带的 rest)。

sell[i]代表的最优解,同理,从第i天是否发生卖出操作分类。

rest[i]就随意多了,第i天肯定不能是buy或者sell,只能是rest。那么从0到i-1天无论什么序列,都是符合要求的。

针对前面rest可能带有的冲突、冗余的“属性”,作者后面又很敏锐地指出,rest[i]其实就等于sell[i-1]!这是因为rest[i]一定大于等于buy[i],而sell[i-1]又大于等于rest[i]。因此上面的第三个等式其实可以直接记作,rest[i]=sell[i-1]。同样的,第一个等式的rest[i-1]-price也可以改写成sell[i-2]-price了。

最后,只要设置好初始量,最后迭代到最后一天即可。而初始量,可以考虑

buy[-1] = - prices[0]

sell[-1] = 0

buy[0] = - prices[0]

sell [0] = 0

设置 -1 下标主要是因为buy[i]需要用到sell[i-2]。

class Solution(object):    def maxProfit(self, prices):        """        :type prices: List[int]        :rtype: int        """        if len(prices) == 0:            return 0        prev_buy = - prices[0]        prev_sell = 0        buy = -prices[0]        sell = 0        for i in range(1,len(prices)):            new_buy = max(buy,prev_sell-prices[i])            new_sell = max(sell,buy+prices[i])            prev_sell = sell            prev_buy = buy            sell = new_sell            buy = new_buy        return max(buy,sell)#print(Solution().maxProfit([1,2,3,0,2]))
0 0