【力扣日记】买卖股票的最佳时机 | 动态规划

文章参考:一个方法团灭六道股票问题——作者:labuladong

动态规划DP

.
.
.

121 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

算法思路

1、穷举

计算所有数对的差值。

class Solution:
    def maxProfit(self, p) -> int:
        Maxnet=0;n=len(p)
        for i in range(0,n):
            for j in range(i,n):
                x=p[j]-p[i]
                if x>Maxnet:
                    Maxnet=x
        return Maxnet

2、迭代

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        profit = 0
        if len(prices)< 2:
            return profit

        else:
            profit = prices[1] - prices[0]
            tmp = max(0,profit)
            for i in range(2,len(prices)):
                tmp += prices[i] - prices[i-1]
                if tmp<0:
                    tmp = 0
                elif tmp>profit:
                    profit = tmp
            return max(profit,0)

执行用时 :44 ms, 在所有 Python3 提交中击败了94.13%的用户
内存消耗 :14.4 MB, 在所有 Python3 提交中击败了17.95%的用户

3、DP

穷举框架。

for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 择优(选择1,选择2...)

代入具体问题:
这个问题的「状态」有三个,第一个是天数,第二个是允许交易的最大次数(这里仅允许交易一次),第三个是当前的持有状态(用 1 表示持有,0 表示没有持有)

每种状态下的选择:买入buy、卖出sell、保持不变rest

dp[i][k][0 or 1]
0 <= i <= n-1, 1 <= k <= K
n 为天数,大 K 为最多交易数
此问题共 n × K × 2 种状态,全部穷举就能搞定。

for 0 <= i < n:
    for 1 <= k <= K:
        for s in {0, 1}:
            dp[i][k][s] = max(buy, sell, rest)

状态转移框架

【力扣日记】买卖股票的最佳时机 | 动态规划_第1张图片
由上图易得到状态转移方程:

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
              max(   选择 rest  ,           选择 sell      )

解释:状态0有两种方式得到:1000

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
              max(   选择 rest  ,           选择 buy         )

解释:状态1有两种方式得到:0111

在两种转移可能里择优选择。

最后给转移方程做初始化:

dp[-1][k][0] = 0
解释:i = -1 意味着还没有开始,这时候利润是 0 。
dp[-1][k][1] = float('-inf')
解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
解释:k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = float('-inf')
解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i]) 
            = max(dp[i-1][1][1], -prices[i])
解释:k = 0 的 base case,所以 dp[i-1][0][0] = 0。

现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。
可以进行进一步化简去掉所有 k:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])

完整算法 I

class Solution:
    def maxProfit(self, p) -> int:
        n=len(p)
        if n==0:return 0
        dp=[[[]for j in range(0,2)]for i in range(0,n)]
        #bace case:dp[-1][0]=0,dp[-1][1]=float('-int')
        dp[0][0]=0;dp[0][1]=-p[0]
        for i in range(1,n):
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + p[i]);
            dp[i][1] = max(dp[i-1][1], -p[i])
        return dp[n-1][0]

原博主提示到,新时刻的两种状态仅与上一刻的两种状态有关,所以不需要整个dp数组,只需要额外两个变量保存上一刻的状态即可。

完整算法 II

class Solution:
    def maxProfit(self, p) -> int:
        n=len(p)
        dp_i_0=0;dp_i_1=float('-inf')
        for i in range(0,n):
            dp_i_0=max(dp_i_0, dp_i_1 + p[i])
            dp_i_1 = max(dp_i_1, -p[i])
        return dp_i_0

122 买卖股票的最佳时机 II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

算法思路

1、方法 I

做一次遍历,计算所有满足条件的差价。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:return 0
        net=0;buy=prices[0]
        for i in prices[1:]:
            if buy<i:net+=i-buy;buy=i
            elif buy>i:buy=i
        return net

2、方法 II

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

3、DP

这道题与第一题的区别在于k无限大。

易得到状态转移方程:

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
            = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i])

正如k=1时k对方程无影响,k=正无穷时对方程也没有影响,可略去。

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])

完整算法 I

class Solution:
    def maxProfit(self, p) -> int:
        n=len(p)
        #特殊情况:p=[]
        if n==0:return 0
        #给出dp数组的初始化
        dp=[[[]for j in range(0,2)]for i in range(0,n)]
        #bace case
        dp[0][0]=0;dp[0][1]=-p[0]
        for i in range(1,n):
            temp=dp[i-1][0] #!!!注意
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + p[i]);
            dp[i][1] = max(dp[i-1][1], temp-p[i])
        return dp[n-1][0]

完整算法 II

class Solution:
    def maxProfit(self, p) -> int:
        n=len(p)
        if n==0:return 0
        dp_i_0=0;dp_i_1=-p[0]
        for i in range(1,n):
            temp=dp_i_0
            dp_i_0 = max(dp_i_0, dp_i_1 + p[i])
            dp_i_1 = max(dp_i_1, temp-p[i])
        return dp_i_0

123 买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

DP

这里的K=2。K=1或无穷时可以忽略,除此以外则必须加入穷举。

class Solution:
    def maxProfit(self, p) -> int:
        k_max = 2
        n = len(p)
        # dp1 = [[[[]] * 2] * (k_max + 1)] * n
		#苦笑。一直做到这里才发现一个巨大的坑,python里通过乘法得到的嵌套列表,
		#修改一个则全部都会变……度娘了一下,发现使用列表生成器可以避免这种错误
		dp=[[[[] for i in range(0,2)] for j in range(0,k_max+1)]for k in range(0,n)]
        for i in range(0, n):
            for k in range(k_max, 0, -1):
                if i == 0:
                    dp[i][k][0] = 0
                    dp[i][k][1] = -p[i]
                    continue
                #k=0时是特殊情况,需要声明
                if k - 1 == 0:
                    # dp[i-1][0][1]=0
                    dp[i - 1][0][0] = 0
                dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + p[i])
                dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - p[i])
                #print('dp[{0}][{1}][0]= max(dp[{3}][{1}][0], dp[{3}][{1}][1] + p[{0}]):{2},'.format(i,k,dp[i][k][0],i-1),'dp[{0}][{1}][1]= max(dp[{3}][{1}][1], dp[{3}][{4}][0] + p[{0}]):{2}'.format(i,k,dp[i][k][0],i-1,k-1))
        return dp[n - 1][k_max][0]

这里 k 取值范围比较小,所以可以不用 for 循环,直接把 k = 1 和 2 的情况手动列举出来也可以:

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][1][0] - prices[i])
dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1], -prices[i])
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        dp_i_10=0
        dp_i_11=float('-inf')
        dp_i_20=0
        dp_i_21=float('-inf')
        for n in prices:
            dp_i_20=max(dp_i_20,dp_i_21+n)
            dp_i_21=max(dp_i_21,dp_i_10-n)
            dp_i_10=max(dp_i_10,dp_i_11+n)
            dp_i_11=max(dp_i_11,-n)
        return dp_i_20

188 买卖股票的最佳时机 IV

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

算法

这是前三题的综合:

class Solution:
    def maxProfit(self, k_max: int, p: List[int]) -> int:
        n=len(p)
        if n==0 or k_max==0:return 0#两种特殊情况
        if k_max==1:#=1时
            dp_i_0=0;dp_i_1=-p[0]
            for i in range(1,n):
                dp_i_0=max(dp_i_0,dp_i_1+p[i])
                dp_i_1=max(dp_i_1,-p[i])
            return dp_i_0
        if k_max>n//2:#k可视为无限时
            dp_i_0=0;dp_i_1=-p[0]
            for i in range(1,n):
                temp=dp_i_0
                dp_i_0=max(dp_i_0,dp_i_1+p[i])
                dp_i_1=max(dp_i_1,temp-p[i])
            return dp_i_0
        else:# 123 题的情况
            dp=[[[[]for i in range(0,2)]for j in range(0,k_max+1)]for k in range(0,n)]
            for i in range(0,n):
                for k in range(k_max,0,-1):
                    if i==0:
                        dp[i][k][0]=0
                        dp[i][k][1]=-p[i]
                        continue
                    if k-1==0:
                        dp[i-1][0][0]=0
                    dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + p[i])
                    dp[i][k][1] = max(dp[i - 1][k][1], dp[i-1][k-1][0]-p[i])
            return dp[n-1][k_max][0]

观摩了一下大佬的答案,发现dp数组的建立是多余的。

class Solution:
    def maxProfit(self, k_max: int, p: List[int]) -> int:
        n=len(p)
        if n==0 or k_max==0:return 0#两种特殊情况
        # if k_max==1:#=1时
        #     dp_i_0=0;dp_i_1=-p[0]
        #     for i in range(1,n):
        #         dp_i_0=max(dp_i_0,dp_i_1+p[i])
        #         dp_i_1=max(dp_i_1,-p[i])
        #     return dp_i_0
        if k_max>n//2:#k可视为无限时
            dp_i_0=0;dp_i_1=-p[0]
            for i in range(1,n):
                temp=dp_i_0
                dp_i_0=max(dp_i_0,dp_i_1+p[i])
                dp_i_1=max(dp_i_1,temp-p[i])
            return dp_i_0
        else:# 123 题的情况
            dp=[[[]for i in range(0,2)]for j in range(0,k_max+1)]
            for i in range(0,n):
                for k in range(k_max,0,-1):
                    if i==0:
                        dp[k][0]=0
                        dp[k][1]=-p[i]
                        continue
                    if k-1==0:
                        dp[0][0]=0
                    dp[k][0] = max(dp[k][0], dp[k][1] + p[i])
                    dp[k][1] = max(dp[k][1], dp[k-1][0]-p[i])
            return dp[k_max][0]
class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        if len(prices) <= 1: return 0
        if (k < len(prices) // 2) :
            dp = [[-prices[0], 0] for i in range(k+1)]
            for price in prices[1:]:
                for i in range(1, k+1):
                    dp[i] = [max(dp[i][0], dp[i-1][1]-price), max(dp[i][1], dp[i][0]+price)]
            return dp[k][1]
        else:
            dp = [-prices[0], 0]
            for price in prices[1:]:
                dp = [max(dp[0], dp[1]-price), max(dp[1], dp[0]+price)]
            return dp[1]

作者:hellozhaozheng
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/solution/tong-yong-fang-fa-de-chao-jian-ji-shi-xian-fu-che-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(力扣日记)